Last active
October 30, 2024 16:19
-
-
Save negast/8d4f8b6512c4835b2d62670fbf8bdc3f to your computer and use it in GitHub Desktop.
nexus copy assets from instance to instance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module nexus_copy | |
go 1.14 | |
require ( | |
github.com/cavaliercoder/grab v2.0.0+incompatible | |
gopkg.in/yaml.v2 v2.4.0 | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"encoding/json" | |
"flag" | |
"fmt" | |
"github.com/cavaliercoder/grab" | |
"gopkg.in/yaml.v2" | |
"io" | |
"io/ioutil" | |
"log" | |
"mime/multipart" | |
"net/http" | |
"os" | |
"strings" | |
) | |
var ( | |
conf = flag.String("conf", "config.yaml", "The path to the config file.") | |
) | |
type NexusAssetsCollection struct { | |
Items []NexusAsset `json:"items"` | |
Token string `json:"continuationToken"` | |
} | |
type NexusAsset struct { | |
DownloadURL string `json:"downloadUrl"` | |
Path string `json:"path"` | |
ID string `json:"id"` | |
Repository string `json:"repository"` | |
Format string `json:"format"` | |
Name string `json:"name"` | |
Checksum struct { | |
Sha1 string `json:"sha1"` | |
Sha512 string `json:"sha512"` | |
Sha256 string `json:"sha256"` | |
Md5 string `json:"md5"` | |
} `json:"checksum"` | |
} | |
type Conf struct { | |
WorkDir string `yaml:"work_dir"` | |
Repo1 string `yaml:"repo1"` | |
Url1 string `yaml:"url1"` | |
User1 string `yaml:"user1"` | |
Pass1 string `yaml:"pass1"` | |
Repo2 string `yaml:"repo2"` | |
Url2 string `yaml:"url2"` | |
User2 string `yaml:"user2"` | |
Pass2 string `yaml:"pass2"` | |
} | |
func (c *Conf) GetConf(configFile string) *Conf { | |
yamlFile, err := ioutil.ReadFile(configFile) | |
if err != nil { | |
log.Printf("yamlFile.Get err #%v ", err) | |
} | |
err = yaml.Unmarshal(yamlFile, c) | |
if err != nil { | |
log.Println("Unmarshal: %v", err) | |
} | |
return c | |
} | |
func downloadAssets(c Conf, assets *NexusAssetsCollection) { | |
for index, a :=range assets.Items { | |
resp, err := grab.Get(c.WorkDir, a.DownloadURL) | |
if err != nil { | |
log.Println(err.Error()) | |
} | |
//assets.Items[index].Name = resp.Filename | |
assets.Items[index].Name = strings.TrimPrefix(resp.Filename, "assets\\") | |
} | |
} | |
func GetAssets(c Conf, token string) NexusAssetsCollection { | |
url := c.Url1 + "/service/rest/v1/assets?repository=" + c.Repo1 | |
if token != "" { | |
url += "&continuationToken=" + token | |
} | |
spaceClient := http.Client{} | |
req, err := http.NewRequest(http.MethodGet, url, nil) | |
if err != nil { | |
log.Println(err) | |
} | |
req.Header.Set("Content-Type", "application/json; charset=UTF-8") | |
req.SetBasicAuth(c.User1, c.Pass1) | |
res, getErr := spaceClient.Do(req) | |
if getErr != nil { | |
log.Println(getErr) | |
} | |
if res.Body != nil { | |
defer res.Body.Close() | |
} | |
body, readErr := ioutil.ReadAll(res.Body) | |
if readErr != nil { | |
log.Fatal(readErr) | |
} | |
//Convert the body to type string | |
//sb := string(body) | |
//log.Printf(sb) | |
assets := NexusAssetsCollection{} | |
jsonErr := json.Unmarshal(body, &assets) | |
if jsonErr != nil { | |
log.Fatal(jsonErr) | |
} | |
if assets.Token != "" { | |
a2 := GetAssets(c, assets.Token) | |
assets.Items = append(assets.Items, a2.Items...) | |
} | |
return assets | |
} | |
func uploadAssets(c Conf, assets *NexusAssetsCollection) { | |
for _, a :=range assets.Items { | |
uploadRawToNexus(c, a) | |
} | |
} | |
func main() { | |
var c Conf | |
c.GetConf(*conf) | |
if c.Url1 == "" { | |
c.Url1 = "http://localhost:9090/nexus" | |
} | |
if c.Url2 == "" { | |
c.Url2 = "http://localhost:8081/nexus" | |
} | |
if c.Repo1 == "" { | |
c.Repo1 = "packages" | |
} | |
if c.Repo2 == "" { | |
c.Repo2 = c.Repo1 | |
} | |
if c.User1 == "" { | |
c.User1 = "admin" | |
} | |
if c.User2 == "" { | |
c.User2 = c.User1 | |
} | |
c.WorkDir = "./assets" | |
os.Mkdir("assets/", 0755) | |
assets := GetAssets(c, "") | |
downloadAssets(c, &assets) | |
uploadAssets(c, &assets) | |
} | |
func uploadRawToNexus(c Conf, a NexusAsset) { | |
// https://stackoverflow.com/questions/20205796/post-data-using-the-content-type-multipart-form-data | |
var client http.Client | |
u := fmt.Sprintf("%s/service/rest/v1/components?repository=%s", c.Url2, c.Repo2) | |
remoteURL := u | |
//prepare the reader instances to encode | |
dir := strings.TrimSuffix(a.Path, a.Name) | |
values := map[string]io.Reader{ | |
a.Format + ".asset": mustOpen(c.WorkDir + "/" + a.Name), // lets assume its this file | |
a.Format + ".asset.filename": strings.NewReader(a.Name), | |
} | |
if dir != "" { | |
values = map[string]io.Reader{ | |
a.Format + ".asset": mustOpen(c.WorkDir + "/" + a.Name), // lets assume its this file | |
a.Format + ".asset.filename": strings.NewReader(a.Name), | |
a.Format + ".directory": strings.NewReader(dir), | |
} | |
} | |
if a.Format == "nuget" { | |
values = map[string]io.Reader{ | |
a.Format + ".asset": mustOpen(c.WorkDir + "/" + a.Name), // lets assume its this file | |
} | |
} | |
err := Upload(&client, remoteURL, values, c) | |
if err != nil { | |
log.Println(err.Error()) | |
} | |
} | |
func Upload(client *http.Client, url string, values map[string]io.Reader, c Conf) (err error) { | |
// Prepare a form that you will submit to that URL. | |
var b bytes.Buffer | |
w := multipart.NewWriter(&b) | |
for key, r := range values { | |
var fw io.Writer | |
if x, ok := r.(io.Closer); ok { | |
defer x.Close() | |
} | |
// Add an image file | |
if x, ok := r.(*os.File); ok { | |
if fw, err = w.CreateFormFile(key, x.Name()); err != nil { | |
return | |
} | |
} else { | |
// Add other fields | |
if fw, err = w.CreateFormField(key); err != nil { | |
return | |
} | |
} | |
if _, err = io.Copy(fw, r); err != nil { | |
return err | |
} | |
} | |
// Don't forget to close the multipart writer. | |
// If you don't close it, your request will be missing the terminating boundary. | |
w.Close() | |
// Now that you have a form, you can submit it to your handler. | |
req, err := http.NewRequest("POST", url, &b) | |
if err != nil { | |
return | |
} | |
// Don't forget to set the content type, this will contain the boundary. | |
req.Header.Set("Content-Type", w.FormDataContentType()) | |
req.SetBasicAuth(c.User2, c.Pass2) | |
// Submit the request | |
res, err := client.Do(req) | |
if err != nil { | |
return | |
} | |
// Check the response | |
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusNoContent { | |
body, _ := ioutil.ReadAll(res.Body) | |
err = fmt.Errorf("bad status: %s", res.Status) | |
log.Println(string(body)) | |
} | |
return | |
} | |
func mustOpen(f string) *os.File { | |
r, err := os.Open(f) | |
if err != nil { | |
log.Fatal(err) | |
} | |
return r | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I ran this from my IDEA the config file looks something like this for me:
You can see in the code on line 138 to ... that it sets some default config that was ok for me. The same is repeated on line 44-54
if c.Url1 == "" {
c.Url1 = "http://localhost:9090/nexus"
}
if c.Url2 == "" {
c.Url2 = "http://localhost:8081/nexus"
}
if c.Repo1 == "" {
c.Repo1 = "packages"
}
if c.Repo2 == "" {
c.Repo2 = c.Repo1
}
if c.User1 == "" {
c.User1 = "admin"
}
if c.User2 == "" {
c.User2 = c.User1
}
this would translate in config.yaml to
In theory I guess you run go build to build this script as well. And then just run it from the local directory. The config.yaml should be in the local dir. I adjusted on line of code my IDEA was giving trouble on.
I only tested this for raw assets though as uploading rpm's/etc might be different upload forms. I do also have a script somewhere to recreate my yum repos etc from instances. But I don't reupload my local ones.