|
package main |
|
|
|
import ( |
|
"crypto/sha256" |
|
"flag" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"net/http" |
|
"os" |
|
"strings" |
|
|
|
"pault.ag/go/debian/deb" |
|
) |
|
|
|
func getName(ctrl deb.Control) (string, error) { |
|
|
|
if ctrl.Package == "" { |
|
return "", fmt.Errorf("Empty Package") |
|
} |
|
|
|
if ctrl.Architecture.CPU == "" { |
|
return "", fmt.Errorf("Missing arch") |
|
} |
|
|
|
return fmt.Sprintf("%s_%s_%s.deb", ctrl.Package, ctrl.Version, ctrl.Architecture.CPU), nil |
|
} |
|
|
|
func getSha256(fpath string) (string, error) { |
|
f, err := os.Open(fpath) |
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
defer f.Close() |
|
h := sha256.New() |
|
if _, err := io.Copy(h, f); err != nil { |
|
return "", err |
|
} |
|
return fmt.Sprintf("%x", h.Sum(nil)), nil |
|
} |
|
|
|
func putDeb(repoURL, fpath, suite, component, creds string) error { |
|
client := http.Client{} |
|
if creds == "" { |
|
return fmt.Errorf("empty creds. sorry") |
|
} |
|
|
|
debInfo, _, err := deb.LoadFile(fpath) |
|
// defer closer() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
ctrl := debInfo.Control |
|
|
|
src := ctrl.Source |
|
// Source is only present in metadata if it differs from Package. |
|
if src == "" { |
|
if ctrl.Package == "" { |
|
return fmt.Errorf("empty 'Source' and no 'Package' in %s", fpath) |
|
} |
|
src = ctrl.Package |
|
} |
|
ver := ctrl.Version.Version |
|
if ver == "" { |
|
return fmt.Errorf("bad (empty) version in %s", fpath) |
|
} |
|
|
|
/* |
|
stza, err := deb.GetControlFileFromDeb(fpath) |
|
if err != nil { |
|
return err |
|
} |
|
*/ |
|
|
|
fp, err := os.Open(fpath) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
defer fp.Close() |
|
fInfo, err := fp.Stat() |
|
if err != nil { |
|
return err |
|
} |
|
localSize := fInfo.Size() |
|
|
|
sha256, err := getSha256(fpath) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
name, err := getName(ctrl) |
|
if err != nil { |
|
return err |
|
} |
|
// name = path.Base(fpath) |
|
|
|
arch := ctrl.Architecture.CPU |
|
// https://www.jfrog.com/confluence/display/JFROG/Debian+Repositories |
|
pubUrl := strings.TrimRight(repoURL, "/") + "/pool/" + src + "/" + ctrl.Version.String() + "/" + name |
|
matrixParms := strings.Join( |
|
[]string{ |
|
"deb.distribution=" + suite, |
|
"deb.component=" + component, |
|
"deb.architecture=" + arch, |
|
}, ";") |
|
|
|
req, err := http.NewRequest(http.MethodHead, pubUrl, nil) |
|
toks := strings.SplitN(creds, ":", 1) |
|
if toks[0] == "" { |
|
// empty creds... assume it is in https://user@pass:... |
|
} else if len(toks) == 1 { |
|
req.Header.Set("X-JFrog-Art-Api", creds) |
|
} else { |
|
req.SetBasicAuth(toks[0], toks[1]) |
|
} |
|
|
|
resp, err := client.Do(req) |
|
|
|
b, err := ioutil.ReadAll(resp.Body) |
|
if err != nil { |
|
return fmt.Errorf("failed readAll HEAD: %v", err) |
|
} |
|
if resp.StatusCode == http.StatusOK { |
|
fmt.Printf("HEAD [%s] %s\n", resp.Status, pubUrl) |
|
rSha256 := resp.Header["X-Checksum-Sha256"][0] |
|
fmt.Printf("remote:\n size: %d\n sha256: %s\n", resp.ContentLength, rSha256) |
|
fmt.Printf("local:\n size: %d\n sha256: %s\n", localSize, sha256) |
|
if localSize != resp.ContentLength { |
|
return fmt.Errorf("local size %d != remote %d for %s\n", localSize, resp.ContentLength, pubUrl) |
|
} |
|
if sha256 != rSha256 { |
|
return fmt.Errorf("local sha256 %s != remote %s for %s\n", sha256, rSha256, pubUrl) |
|
} |
|
// for publishing to new suite |
|
fmt.Printf("Existing matched local, will upload again.\n") |
|
} else if resp.StatusCode == http.StatusNotFound { |
|
fmt.Printf("New upload. Did not exist\n") |
|
} else { |
|
return fmt.Errorf("Got unexpectd status code %d on HEAD", resp.StatusCode) |
|
} |
|
|
|
req, err = http.NewRequest(http.MethodPut, pubUrl+";"+matrixParms, fp) |
|
if err != nil { |
|
return err |
|
} |
|
toks = strings.SplitN(creds, ":", 1) |
|
if toks[0] == "" { |
|
// empty creds... assume it is in https://user@pass:... |
|
} else if len(toks) == 1 { |
|
req.Header.Set("X-JFrog-Art-Api", creds) |
|
} else { |
|
req.SetBasicAuth(toks[0], toks[1]) |
|
} |
|
req.Header.Set("X-Checksum-Sha256", sha256) |
|
|
|
resp, err = client.Do(req) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
fmt.Printf("Posted [%s] %s -> %s\n", resp.Status, fpath, pubUrl) |
|
b, err = ioutil.ReadAll(resp.Body) |
|
if err != nil { |
|
return fmt.Errorf("failed readAll: %v", err) |
|
} |
|
fmt.Printf("%s\n", string(b)) |
|
return nil |
|
} |
|
|
|
func showDeb(fpath string) error { |
|
debInfo, closer, err := deb.LoadFile(fpath) |
|
defer closer() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
ver := debInfo.Control.Version |
|
fmt.Printf("Version:\n epoch: %d\n version: %s\n revision: %s\n str: %s\n", |
|
ver.Epoch, ver.Version, ver.Revision, ver.String()) |
|
return nil |
|
} |
|
|
|
func main() { |
|
const credEnvName = "ARTIFACTORY_CREDS" |
|
const component = "main" |
|
var creds, suite string |
|
|
|
flag.StringVar(&creds, "creds", "", "api token or user:pass for artifactory (env "+credEnvName+"_") |
|
flag.StringVar(&suite, "suite", "focal", "suite to publish to (focal)") |
|
flag.Bool("v", false, "verbose") |
|
flag.Parse() |
|
|
|
args := flag.Args() |
|
|
|
if len(args) <= 1 { |
|
fmt.Printf("Must give args [repo, debs]\n") |
|
os.Exit(1) |
|
} |
|
|
|
repo := args[0] |
|
if !strings.HasSuffix(repo, "/") { |
|
repo += "/" |
|
} |
|
|
|
if creds == "" { |
|
creds = os.Getenv(credEnvName) |
|
if creds == "" { |
|
fmt.Printf("Must provide creds either --creds= or env %s\n", credEnvName) |
|
} |
|
} |
|
|
|
for _, p := range args[1:] { |
|
// showDeb(p) |
|
if err := putDeb(repo, p, suite, component, creds); err != nil { |
|
fmt.Printf("Failed putting deb %s: %v\n", p, err) |
|
os.Exit(1) |
|
} |
|
} |
|
|
|
} |