mirror of
https://tvoygit.ru/Djam/artmigrator.git
synced 2025-02-23 10:22:45 +00:00
Add Dockerfile, rewrite source
This commit is contained in:
parent
a9bbbf458c
commit
499b064598
17 changed files with 562 additions and 0 deletions
20
Dockerfile
Normal file
20
Dockerfile
Normal file
|
@ -0,0 +1,20 @@
|
|||
FROM docker.io/golang:1.23-alpine AS builder
|
||||
|
||||
COPY . /build
|
||||
|
||||
RUN apk add --no-cache git && \
|
||||
ping -c5 tvoygit.ru && \
|
||||
export GOPROXY=direct && \
|
||||
cd /build && \
|
||||
go build -o ./app .
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /build/app /app
|
||||
|
||||
CMD [/app]
|
||||
|
||||
|
||||
|
||||
|
||||
|
53
artifact_types/maven.go
Normal file
53
artifact_types/maven.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package artifact_types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type MavenArtifact struct {
|
||||
GroupID string
|
||||
ArtifactID string
|
||||
Version string
|
||||
Packaging string
|
||||
}
|
||||
|
||||
func (a MavenArtifact) String() string {
|
||||
return fmt.Sprintf("%s:%s:%s:%s", a.GroupID, a.ArtifactID, a.Version, a.Packaging)
|
||||
}
|
||||
|
||||
func ParseMavenArtifact(artifact string) (*MavenArtifact, error) {
|
||||
parts := splitArtifact(artifact, 4)
|
||||
if len(parts) < 4 {
|
||||
return nil, fmt.Errorf("invalid Maven artifact format: %s", artifact)
|
||||
}
|
||||
return &MavenArtifact{
|
||||
GroupID: parts[0],
|
||||
ArtifactID: parts[1],
|
||||
Version: parts[2],
|
||||
Packaging: parts[3],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func splitArtifact(artifact string, n int) []string {
|
||||
parts := make([]string, n)
|
||||
for i := 0; i < n-1; i++ {
|
||||
index := findNextColon(artifact)
|
||||
if index == -1 {
|
||||
break
|
||||
}
|
||||
parts[i] = artifact[:index]
|
||||
artifact = artifact[index+1:]
|
||||
}
|
||||
parts[n-1] = artifact
|
||||
return parts
|
||||
}
|
||||
|
||||
func findNextColon(s string) int {
|
||||
for i, r := range s {
|
||||
if r == ':' {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
48
artifact_types/maven_test.go
Normal file
48
artifact_types/maven_test.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package artifact_types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseMavenArtifact(t *testing.T) {
|
||||
tests := []struct {
|
||||
artifact string
|
||||
expected *MavenArtifact
|
||||
}{
|
||||
{
|
||||
artifact: "com.example:my-artifact:1.0.0:jar",
|
||||
expected: &MavenArtifact{
|
||||
GroupID: "com.example",
|
||||
ArtifactID: "my-artifact",
|
||||
Version: "1.0.0",
|
||||
Packaging: "jar",
|
||||
},
|
||||
},
|
||||
{
|
||||
artifact: "org.apache.commons:commons-lang3:3.12.0:jar",
|
||||
expected: &MavenArtifact{
|
||||
GroupID: "org.apache.commons",
|
||||
ArtifactID: "commons-lang3",
|
||||
Version: "3.12.0",
|
||||
Packaging: "jar",
|
||||
},
|
||||
},
|
||||
{
|
||||
artifact: "invalid-artifact",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
actual, err := ParseMavenArtifact(test.artifact)
|
||||
if test.expected == nil {
|
||||
assert.Error(t, err, "Expected error for invalid artifact: %s", test.artifact)
|
||||
} else {
|
||||
assert.NoError(t, err, "Unexpected error for valid artifact: %s", test.artifact)
|
||||
assert.Equal(t, test.expected, actual, "Mismatched parsed Maven artifact for: %s", test.artifact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
34
artifact_types/rpm.go
Normal file
34
artifact_types/rpm.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package artifact_types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RpmArtifact struct {
|
||||
Name string
|
||||
Version string
|
||||
Release string
|
||||
Arch string
|
||||
}
|
||||
|
||||
func (a RpmArtifact) String() string {
|
||||
return fmt.Sprintf("%s-%s-%s.%s.rpm", a.Name, a.Version, a.Release, a.Arch)
|
||||
}
|
||||
|
||||
var rpmRegex = regexp.MustCompile(`^(.+)-([^-]+)-([^-]+)\.(.+)\.rpm$`)
|
||||
|
||||
func ParseRpmArtifact(artifact string) (*RpmArtifact, error) {
|
||||
matches := rpmRegex.FindStringSubmatch(artifact)
|
||||
if matches == nil || len(matches) != 5 {
|
||||
return nil, fmt.Errorf("invalid RPM artifact format: %s", artifact)
|
||||
}
|
||||
return &RpmArtifact{
|
||||
Name: matches[1],
|
||||
Version: matches[2],
|
||||
Release: matches[3],
|
||||
Arch: matches[4],
|
||||
}, nil
|
||||
}
|
||||
|
52
artifact_types/rpm_test.go
Normal file
52
artifact_types/rpm_test.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package artifact_types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseRpmArtifact(t *testing.T) {
|
||||
tests := []struct {
|
||||
artifact string
|
||||
expected *RpmArtifact
|
||||
}{
|
||||
{
|
||||
artifact: "example-1.0.0-1.x86_64.rpm",
|
||||
expected: &RpmArtifact{
|
||||
Name: "example",
|
||||
Version: "1.0.0",
|
||||
Release: "1",
|
||||
Arch: "x86_64",
|
||||
},
|
||||
},
|
||||
{
|
||||
artifact: "my-package-2.3.4-5.el7.noarch.rpm",
|
||||
expected: &RpmArtifact{
|
||||
Name: "my-package",
|
||||
Version: "2.3.4",
|
||||
Release: "5.el7",
|
||||
Arch: "noarch",
|
||||
},
|
||||
},
|
||||
{
|
||||
artifact: "invalid-artifact.rpm",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
artifact: "invalid-artifact",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
actual, err := ParseRpmArtifact(test.artifact)
|
||||
if test.expected == nil {
|
||||
assert.Error(t, err, "Expected error for invalid artifact: %s", test.artifact)
|
||||
} else {
|
||||
assert.NoError(t, err, "Unexpected error for valid artifact: %s", test.artifact)
|
||||
assert.Equal(t, test.expected, actual, "Mismatched parsed RPM artifact for: %s", test.artifact)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
config.yaml.template
Normal file
10
config.yaml.template
Normal file
|
@ -0,0 +1,10 @@
|
|||
nexus:
|
||||
url: "https://nexus.example.com"
|
||||
username: "your-nexus-username"
|
||||
password: "your-nexus-password"
|
||||
|
||||
gitea:
|
||||
url: "https://gitea.example.com"
|
||||
token: "your-gitea-token"
|
||||
|
||||
|
34
config/config.go
Normal file
34
config/config.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Nexus struct {
|
||||
URL string `yaml:"url"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
} `yaml:"nexus"`
|
||||
Gitea struct {
|
||||
URL string `yaml:"url"`
|
||||
Token string `yaml:"token"`
|
||||
} `yaml:"gitea"`
|
||||
}
|
||||
|
||||
func LoadConfig(filename string) (*Config, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
err = yaml.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
19
config/config_test.go
Normal file
19
config/config_test.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadConfig(t *testing.T) {
|
||||
configFile := "config.yaml"
|
||||
cfg, err := LoadConfig(configFile)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, cfg)
|
||||
assert.Equal(t, "https://nexus.example.com", cfg.Nexus.URL)
|
||||
assert.Equal(t, "your-nexus-username", cfg.Nexus.Username)
|
||||
assert.Equal(t, "your-nexus-password", cfg.Nexus.Password)
|
||||
assert.Equal(t, "https://gitea.example.com", cfg.Gitea.URL)
|
||||
assert.Equal(t, "your-gitea-token", cfg.Gitea.Token)
|
||||
}
|
66
core/core.go
Normal file
66
core/core.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"tvoygit.ru/djam/artmigrator/config"
|
||||
"tvoygit.ru/djam/artmigrator/logger"
|
||||
"tvoygit.ru/djam/artmigrator/repository/gitea"
|
||||
"tvoygit.ru/djam/artmigrator/repository/nexus"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
func NewApp(configFile string) (*App, error) {
|
||||
cfg, err := config.LoadConfig(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
flag.StringVar(&cfg.Nexus.URL, "nexus-url", cfg.Nexus.URL, "Nexus URL")
|
||||
flag.StringVar(&cfg.Nexus.Username, "nexus-username", cfg.Nexus.Username, "Nexus Username")
|
||||
flag.StringVar(&cfg.Nexus.Password, "nexus-password", cfg.Nexus.Password, "Nexus Password")
|
||||
flag.StringVar(&cfg.Gitea.URL, "gitea-url", cfg.Gitea.URL, "Gitea URL")
|
||||
flag.StringVar(&cfg.Gitea.Token, "gitea-token", cfg.Gitea.Token, "Gitea Token")
|
||||
flag.Parse()
|
||||
|
||||
return &App{Config: cfg}, nil
|
||||
}
|
||||
|
||||
func (a *App) Run() error {
|
||||
nexusClient := nexus.NewClient(a.Config.Nexus.URL, a.Config.Nexus.Username, a.Config.Nexus.Password)
|
||||
giteaClient := gitea.NewClient(a.Config.Gitea.URL, a.Config.Gitea.Token)
|
||||
|
||||
repository := "your-repository-name"
|
||||
artifacts, err := nexusClient.GetArtifacts(repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, artifact := range artifacts {
|
||||
exists, err := giteaClient.ArtifactExists("owner", "repo", artifact)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
logger.Logger.Printf("Artifact %s already exists in Gitea", artifact)
|
||||
continue
|
||||
}
|
||||
|
||||
// Загрузка артефакта
|
||||
data, err := nexusClient.GetArtifactData(repository, artifact)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = giteaClient.UploadArtifact("owner", "repo", artifact, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Logger.Printf("Artifact %s uploaded to Gitea", artifact)
|
||||
}
|
||||
return nil
|
||||
}
|
13
go.mod
Normal file
13
go.mod
Normal file
|
@ -0,0 +1,13 @@
|
|||
module github.com/yourusername/art_migrator
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.9.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
)
|
17
go.sum
Normal file
17
go.sum
Normal file
|
@ -0,0 +1,17 @@
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
12
logger/logger.go
Normal file
12
logger/logger.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var Logger *log.Logger
|
||||
|
||||
func init() {
|
||||
Logger = log.New(os.Stdout, "", log.LstdFlags)
|
||||
}
|
19
main.go
Normal file
19
main.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"tvoygit.ru/djam/artmigrator/core"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app, err := core.NewApp("config.yaml")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create app: %v", err)
|
||||
}
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to run app: %v", err)
|
||||
}
|
||||
}
|
66
repository/gitea/gitea.go
Normal file
66
repository/gitea/gitea.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"tvoygit.ru/djam/artmigrator/logger"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
HTTPClient *http.Client
|
||||
Token string
|
||||
}
|
||||
|
||||
func NewClient(baseURL, token string) *Client {
|
||||
return &Client{
|
||||
BaseURL: baseURL,
|
||||
HTTPClient: &http.Client{},
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) ArtifactExists(owner, repo, artifact string) (bool, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/contents/%s", c.BaseURL, owner, repo, artifact)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("token %s", c.Token))
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *Client) UploadArtifact(owner, repo, artifactPath string, data []byte) error {
|
||||
url := fmt.Sprintf("%s/api/v1/repos/%s/%s/contents/%s", c.BaseURL, owner, repo, artifactPath)
|
||||
req, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Authorization", fmt.Sprintf("token %s", c.Token))
|
||||
|
||||
// Здесь нужно заполнить тело запроса данными о загружаемом артефакте
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
logger.Logger.Printf("Failed to upload artifact: %s", body)
|
||||
return fmt.Errorf("failed to upload artifact")
|
||||
}
|
||||
return nil
|
||||
}
|
14
repository/gitea/gitea_test.go
Normal file
14
repository/gitea/gitea_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package gitea
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArtifactExists(t *testing.T) {
|
||||
client := NewClient("https://gitea.example.com", "token")
|
||||
exists, err := client.ArtifactExists("owner", "repo", "test-artifact")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, false, exists)
|
||||
}
|
71
repository/nexus/nexus.go
Normal file
71
repository/nexus/nexus.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package nexus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"tvoygit.ru/djam/artmigrator/logger"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
HTTPClient *http.Client
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func NewClient(baseURL, username, password string) *Client {
|
||||
return &Client{
|
||||
BaseURL: baseURL,
|
||||
HTTPClient: &http.Client{},
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetArtifacts(repository string) ([]string, error) {
|
||||
url := fmt.Sprintf("%s/repository/%s/", c.BaseURL, repository)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.SetBasicAuth(c.Username, c.Password)
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Здесь нужно парсить ответ и вытащить список артефактов
|
||||
artifacts := strings.Split(string(body), "\n")
|
||||
return artifacts, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetArtifactData(repository, artifact string) ([]byte, error) {
|
||||
url := fmt.Sprintf("%s/repository/%s/%s", c.BaseURL, repository, artifact)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.SetBasicAuth(c.Username, c.Password)
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
14
repository/nexus/nexus_test.go
Normal file
14
repository/nexus/nexus_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package nexus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetArtifacts(t *testing.T) {
|
||||
client := NewClient("https://nexus.example.com", "user", "pass")
|
||||
artifacts, err := client.GetArtifacts("test-repo")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, artifacts)
|
||||
}
|
Loading…
Add table
Reference in a new issue