Skip to content

cmd/go: package modules into mod format for upload #34104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/cmd/go/internal/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/cmd/go/internal/envcmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/go/internal/modcmd/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ See 'go help modules' for an overview of module functionality.
cmdEdit,
cmdGraph,
cmdInit,
cmdPack,
cmdPush,
cmdTidy,
cmdVendor,
cmdVerify,
Expand Down
41 changes: 41 additions & 0 deletions src/cmd/go/internal/modcmd/pack.go
Original file line number Diff line number Diff line change
@@ -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 [<module> <version>]",
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
}
61 changes: 61 additions & 0 deletions src/cmd/go/internal/modcmd/push.go
Original file line number Diff line number Diff line change
@@ -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 <vcs>/<owner>/<name> -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 <vcs>/owner>/<name>, 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 <vcs>/<owner>/<name>")
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 <vcs>/<owner>/<name> (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)
}
135 changes: 135 additions & 0 deletions src/cmd/go/internal/modpack/pack.go
Original file line number Diff line number Diff line change
@@ -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
}
60 changes: 60 additions & 0 deletions src/cmd/go/internal/modpack/packInfo.go
Original file line number Diff line number Diff line change
@@ -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"
}
40 changes: 40 additions & 0 deletions src/cmd/go/internal/modpush/push.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 1 addition & 0 deletions src/internal/cfg/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const KnownEnv = `
GOPATH
GOPPC64
GOPRIVATE
GOPUSH
GOPROXY
GOROOT
GOSUMDB
Expand Down