This commit is contained in:
parent
db49ea2c51
commit
6693ceabcc
8 changed files with 275 additions and 167 deletions
|
@ -1,4 +1,3 @@
|
||||||
.git
|
|
||||||
app.db
|
app.db
|
||||||
uploads
|
uploads
|
||||||
Dockerfile
|
Dockerfile
|
|
@ -5,7 +5,10 @@ WORKDIR /app
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN make release
|
|
||||||
|
ARG APP_VERSION
|
||||||
|
|
||||||
|
RUN make build
|
||||||
|
|
||||||
FROM registry.altlinux.org/sisyphus/alt:20241211
|
FROM registry.altlinux.org/sisyphus/alt:20241211
|
||||||
|
|
||||||
|
|
14
Makefile
14
Makefile
|
@ -1,7 +1,10 @@
|
||||||
GIT_VERSION = $(shell git describe --tags )
|
|
||||||
GOLANGCI_LINT := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
|
GOLANGCI_LINT := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2
|
||||||
SWAG := go run github.com/swaggo/swag/cmd/swag@v1.16.4
|
SWAG := go run github.com/swaggo/swag/cmd/swag@v1.16.4
|
||||||
|
|
||||||
|
GIT_VERSION = $(shell git describe --tags)
|
||||||
|
APP_VERSION ?= $(GIT_VERSION)
|
||||||
|
DOCKER_TAG ?= $(APP_VERSION)
|
||||||
|
|
||||||
format:
|
format:
|
||||||
@echo "🛠️ Format and Lint code with golangci-lint"
|
@echo "🛠️ Format and Lint code with golangci-lint"
|
||||||
$(GOLANGCI_LINT) run
|
$(GOLANGCI_LINT) run
|
||||||
|
@ -19,16 +22,19 @@ swag:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build \
|
go build \
|
||||||
-ldflags="-X 'code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config.Version=$(GIT_VERSION)'" \
|
-ldflags="-X 'code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config.Version=$(APP_VERSION)'" \
|
||||||
-o aides-repo-api \
|
-o aides-repo-api \
|
||||||
./cmd/aides-repo-api/main.go
|
./cmd/aides-repo-api/main.go
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go build \
|
go build \
|
||||||
-ldflags="-s -w -X 'code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config.Version=$(GIT_VERSION)'" \
|
-ldflags="-s -w -X 'code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config.Version=$(APP_VERSION)'" \
|
||||||
-o aides-repo-api \
|
-o aides-repo-api \
|
||||||
./cmd/aides-repo-api/main.go
|
./cmd/aides-repo-api/main.go
|
||||||
|
|
||||||
build-docker:
|
build-docker:
|
||||||
docker build -t ghcr.io/aides-infra/aides-repo-api .
|
docker build \
|
||||||
|
--build-arg APP_VERSION=${APP_VERSION} \
|
||||||
|
-t ghcr.io/aides-infra/aides-repo-api:${DOCKER_TAG}\
|
||||||
|
.
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,6 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Init()
|
app.Init()
|
||||||
|
defer app.Shutdown()
|
||||||
app.Run()
|
app.Run()
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,10 +121,20 @@ func (app *App) Run() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
app.repo.ForceUpdate()
|
err := app.repo.ForceUpdate()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err := http.ListenAndServe(fmt.Sprintf(":%d", app.config.Port), app.router.Setup())
|
err = http.ListenAndServe(
|
||||||
|
fmt.Sprintf(":%d", app.config.Port),
|
||||||
|
app.router.Setup(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *App) Shutdown() {
|
||||||
|
app.cron.Shutdown()
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
package cronservice
|
package cronservice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/go-co-op/gocron/v2"
|
"github.com/go-co-op/gocron/v2"
|
||||||
|
|
||||||
"code.alt-gnome.ru/aides-infra/aides-repo-api/internal/logger"
|
"code.alt-gnome.ru/aides-infra/aides-repo-api/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RepoService interface {
|
type RepoService interface {
|
||||||
ForceUpdate()
|
ForceUpdate() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
@ -19,9 +17,12 @@ type Service struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(repo RepoService) *Service {
|
func New(repo RepoService) *Service {
|
||||||
|
log := logger.GetLogger()
|
||||||
scheduler, err := gocron.NewScheduler()
|
scheduler, err := gocron.NewScheduler()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Не удалось создать планировщик: %v", err)
|
log.Fatal("Не удалось создать планировщик", map[string]interface{}{
|
||||||
|
"err": err,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
|
@ -31,18 +32,25 @@ func New(repo RepoService) *Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SetupCronJobs() {
|
func (s *Service) SetupCronJobs() {
|
||||||
|
log := logger.GetLogger()
|
||||||
_, err := s.scheduler.NewJob(
|
_, err := s.scheduler.NewJob(
|
||||||
gocron.CronJob("0 4 * * *", false),
|
gocron.CronJob("0 4 * * *", false),
|
||||||
gocron.NewTask(func() {
|
gocron.NewTask(func() {
|
||||||
log := logger.GetLogger()
|
|
||||||
log.Info(
|
log.Info(
|
||||||
"[cron] force update is started",
|
"[cron] force update is started",
|
||||||
)
|
)
|
||||||
s.repoService.ForceUpdate()
|
err := s.repoService.ForceUpdate()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[cron] force update error", map[string]interface{}{
|
||||||
|
"err": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Не удалось создать задание cron: %v", err)
|
log.Fatal("Не удалось создать задание cron", map[string]interface{}{
|
||||||
|
"err": err,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,8 +59,11 @@ func (s *Service) Start() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Shutdown() {
|
func (s *Service) Shutdown() {
|
||||||
|
log := logger.GetLogger()
|
||||||
err := s.scheduler.Shutdown()
|
err := s.scheduler.Shutdown()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Не удалось корректно завершить работу планировщика: %v", err)
|
log.Error("Не удалось корректно завершить работу планировщика", map[string]interface{}{
|
||||||
|
"err": err,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package reposervice
|
package reposervice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -29,170 +27,160 @@ func New(db *gorm.DB, cfg Config) *Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSymlink(target, link string) error {
|
func (s *Service) futureRepoPathPrefix() string {
|
||||||
if _, err := os.Lstat(link); err == nil {
|
return path.Join(
|
||||||
if err := os.Remove(link); err != nil {
|
s.config.GetUploadDir(),
|
||||||
return fmt.Errorf("failed to remove existing file or symlink: %w", err)
|
".future_repo",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) currentRepoPathPrefix() string {
|
||||||
|
return path.Join(
|
||||||
|
s.config.GetUploadDir(),
|
||||||
|
"repo",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) oldRepoPathPrefix() string {
|
||||||
|
return path.Join(
|
||||||
|
s.config.GetUploadDir(),
|
||||||
|
".old_repo",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ForceUpdate() error {
|
||||||
|
const REPO_NAME = "aides"
|
||||||
|
|
||||||
|
architectures := []string{
|
||||||
|
"x86_64",
|
||||||
|
"noarch",
|
||||||
|
}
|
||||||
|
|
||||||
|
repos := []string{
|
||||||
|
"Sisyphus",
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.GetLogger()
|
||||||
|
log.Info("Start repo update")
|
||||||
|
|
||||||
|
err := os.MkdirAll(s.futureRepoPathPrefix(), os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(s.futureRepoPathPrefix())
|
||||||
|
|
||||||
|
err = os.MkdirAll(s.oldRepoPathPrefix(), os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(s.oldRepoPathPrefix())
|
||||||
|
|
||||||
|
err = os.MkdirAll(s.currentRepoPathPrefix(), os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range repos {
|
||||||
|
var tasks []models.Task
|
||||||
|
altRepo := models.ALTRepo{
|
||||||
|
Name: r,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Symlink(target, link); err != nil {
|
s.db.
|
||||||
return fmt.Errorf("failed to create symlink: %w", err)
|
Where(&altRepo).
|
||||||
}
|
First(&altRepo)
|
||||||
|
|
||||||
return nil
|
s.db.
|
||||||
}
|
Model(&models.GitRepoAltRepoTask{}).
|
||||||
|
Select("tasks.*").
|
||||||
|
Joins("JOIN tasks ON tasks.id = git_repo_alt_repo_tasks.last_task_id").
|
||||||
|
Where(&models.GitRepoAltRepoTask{
|
||||||
|
ALTRepoID: altRepo.ID,
|
||||||
|
}).
|
||||||
|
Preload("Files").
|
||||||
|
Find(&tasks)
|
||||||
|
|
||||||
func runGenbasedir(repoDir, arch, repoName string) {
|
futureRepoPath := path.Join(
|
||||||
log := logger.GetLogger()
|
s.futureRepoPathPrefix(),
|
||||||
cmd := exec.Command(
|
altRepo.Name,
|
||||||
"genbasedir",
|
|
||||||
"--bloat",
|
|
||||||
"--progress",
|
|
||||||
fmt.Sprintf("--topdir=%s", repoDir),
|
|
||||||
arch,
|
|
||||||
repoName,
|
|
||||||
)
|
|
||||||
cmd.Stdout = nil
|
|
||||||
cmd.Stderr = nil
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(
|
|
||||||
"Failed to run genbasedir",
|
|
||||||
map[string]interface{}{
|
|
||||||
"arch": arch,
|
|
||||||
"error": err,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
log.Info("Successfully ran genbasedir", map[string]interface{}{
|
|
||||||
"arch": arch,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRepoDirs(repoDir, repoName, arch string) {
|
err := os.MkdirAll(
|
||||||
log := logger.GetLogger()
|
futureRepoPath,
|
||||||
// Create the 'base' directory
|
os.ModePerm,
|
||||||
baseDir := path.Join(repoDir, arch, "base")
|
)
|
||||||
err := os.MkdirAll(baseDir, os.ModePerm)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
log.Error("Failed to create directory", map[string]interface{}{
|
}
|
||||||
"directory": baseDir,
|
|
||||||
"err": err,
|
|
||||||
})
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the 'RPMS.<REPO_NAME>' directory
|
for _, arch := range architectures {
|
||||||
rpmsDir := path.Join(repoDir, arch, fmt.Sprintf("RPMS.%s", repoName))
|
err = createRepoDirs(futureRepoPath, REPO_NAME, arch)
|
||||||
err = os.MkdirAll(rpmsDir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to create directory", map[string]interface{}{
|
|
||||||
"directory": rpmsDir,
|
|
||||||
"err": err,
|
|
||||||
})
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) ForceUpdate() {
|
|
||||||
var tasks []models.Task
|
|
||||||
altRepo := models.ALTRepo{
|
|
||||||
Name: "Sisyphus",
|
|
||||||
}
|
|
||||||
|
|
||||||
s.db.
|
|
||||||
Where(&altRepo).
|
|
||||||
First(&altRepo)
|
|
||||||
|
|
||||||
s.db.
|
|
||||||
Model(&models.GitRepoAltRepoTask{}).
|
|
||||||
Select("tasks.*").
|
|
||||||
Joins("JOIN tasks ON tasks.id = git_repo_alt_repo_tasks.last_task_id").
|
|
||||||
Where(&models.GitRepoAltRepoTask{
|
|
||||||
ALTRepoID: altRepo.ID,
|
|
||||||
}).
|
|
||||||
Preload("Files").
|
|
||||||
Find(&tasks)
|
|
||||||
|
|
||||||
repoPath := path.Join(s.config.GetUploadDir(), "future_repo", "Sisyphus")
|
|
||||||
|
|
||||||
err := os.MkdirAll(
|
|
||||||
repoPath,
|
|
||||||
os.ModePerm,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repoName := "aides"
|
|
||||||
architectures := []string{"x86_64", "noarch"}
|
|
||||||
|
|
||||||
for _, arch := range architectures {
|
|
||||||
createRepoDirs(repoPath, repoName, arch)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, el := range tasks {
|
|
||||||
for _, fileInfo := range el.Files {
|
|
||||||
localFilePath := path.Join(
|
|
||||||
strconv.FormatUint(uint64(el.ID), 10), fileInfo.Name,
|
|
||||||
)
|
|
||||||
symLink := path.Join(
|
|
||||||
s.config.GetUploadDir(),
|
|
||||||
"future_repo",
|
|
||||||
"Sisyphus",
|
|
||||||
fileInfo.Arch,
|
|
||||||
"RPMS.aides",
|
|
||||||
fileInfo.Name,
|
|
||||||
)
|
|
||||||
targetPath := path.Join("../../../../tasks/", localFilePath)
|
|
||||||
err := createSymlink(targetPath, symLink)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for _, arch := range architectures {
|
for _, el := range tasks {
|
||||||
runGenbasedir(repoPath, arch, repoName)
|
for _, fileInfo := range el.Files {
|
||||||
}
|
localFilePath := path.Join(
|
||||||
|
strconv.FormatUint(uint64(el.ID), 10), fileInfo.Name,
|
||||||
|
)
|
||||||
|
symLink := path.Join(
|
||||||
|
futureRepoPath,
|
||||||
|
fileInfo.Arch,
|
||||||
|
"RPMS.aides",
|
||||||
|
fileInfo.Name,
|
||||||
|
)
|
||||||
|
targetPath := path.Join("../../../../tasks/", localFilePath)
|
||||||
|
err := createSymlink(targetPath, symLink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.db.
|
for _, arch := range architectures {
|
||||||
Model(&models.GitRepoAltRepoTask{}).
|
err = runGenbasedir(futureRepoPath, arch, REPO_NAME)
|
||||||
Where(&models.GitRepoAltRepoTask{
|
if err != nil {
|
||||||
ALTRepoID: altRepo.ID,
|
return err
|
||||||
}).
|
}
|
||||||
Update(
|
}
|
||||||
"current_task_id", gorm.Expr("last_task_id"),
|
|
||||||
|
s.db.
|
||||||
|
Model(&models.GitRepoAltRepoTask{}).
|
||||||
|
Where(&models.GitRepoAltRepoTask{
|
||||||
|
ALTRepoID: altRepo.ID,
|
||||||
|
}).
|
||||||
|
Update(
|
||||||
|
"current_task_id", gorm.Expr("last_task_id"),
|
||||||
|
)
|
||||||
|
|
||||||
|
currentRepoPath := path.Join(
|
||||||
|
s.currentRepoPathPrefix(),
|
||||||
|
altRepo.Name,
|
||||||
)
|
)
|
||||||
|
|
||||||
err = os.MkdirAll(path.Join(s.config.GetUploadDir(), "repo"), os.ModePerm)
|
oldRepoPath := path.Join(
|
||||||
if err != nil {
|
s.oldRepoPathPrefix(),
|
||||||
panic(err)
|
altRepo.Name,
|
||||||
}
|
)
|
||||||
|
|
||||||
aPath := path.Join(s.config.GetUploadDir(), "future_repo", "Sisyphus")
|
if err := renameIfExists(
|
||||||
bPath := path.Join(s.config.GetUploadDir(), "repo", "Sisyphus")
|
currentRepoPath,
|
||||||
cPath := path.Join(s.config.GetUploadDir(), "repo", ".Sisyphus")
|
oldRepoPath,
|
||||||
|
); err != nil {
|
||||||
if _, err := os.Stat(bPath); err == nil {
|
return err
|
||||||
if err := os.Rename(bPath, cPath); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
panic(err)
|
if err := os.Rename(
|
||||||
|
futureRepoPath,
|
||||||
|
currentRepoPath,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(aPath, bPath); err != nil {
|
log.Info("Successful repo update")
|
||||||
panic(err)
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.RemoveAll(cPath); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.RemoveAll(path.Join(s.config.GetUploadDir(), "future_repo")); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
91
internal/services/reposervice/utils.go
Normal file
91
internal/services/reposervice/utils.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package reposervice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"code.alt-gnome.ru/aides-infra/aides-repo-api/internal/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createRepoDirs(repoDir, repoName, arch string) error {
|
||||||
|
log := logger.GetLogger()
|
||||||
|
// Create the 'base' directory
|
||||||
|
baseDir := path.Join(repoDir, arch, "base")
|
||||||
|
err := os.MkdirAll(baseDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create directory", map[string]interface{}{
|
||||||
|
"directory": baseDir,
|
||||||
|
"err": err,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the 'RPMS.<REPO_NAME>' directory
|
||||||
|
rpmsDir := path.Join(repoDir, arch, fmt.Sprintf("RPMS.%s", repoName))
|
||||||
|
err = os.MkdirAll(rpmsDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to create directory", map[string]interface{}{
|
||||||
|
"directory": rpmsDir,
|
||||||
|
"err": err,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runGenbasedir(repoDir, arch, repoName string) error {
|
||||||
|
log := logger.GetLogger()
|
||||||
|
cmd := exec.Command(
|
||||||
|
"genbasedir",
|
||||||
|
"--bloat",
|
||||||
|
"--progress",
|
||||||
|
fmt.Sprintf("--topdir=%s", repoDir),
|
||||||
|
arch,
|
||||||
|
repoName,
|
||||||
|
)
|
||||||
|
cmd.Stdout = nil
|
||||||
|
cmd.Stderr = nil
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(
|
||||||
|
"Failed to run genbasedir",
|
||||||
|
map[string]interface{}{
|
||||||
|
"arch": arch,
|
||||||
|
"error": err,
|
||||||
|
"cmd_error": cmd.Stderr,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug("Successfully ran genbasedir", map[string]interface{}{
|
||||||
|
"arch": arch,
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSymlink(target, link string) error {
|
||||||
|
if _, err := os.Lstat(link); err == nil {
|
||||||
|
if err := os.Remove(link); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove existing file or symlink: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Symlink(target, link); err != nil {
|
||||||
|
return fmt.Errorf("failed to create symlink: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renameIfExists(oldPath, newPath string) error {
|
||||||
|
if _, err := os.Stat(oldPath); err == nil {
|
||||||
|
return os.Rename(oldPath, newPath)
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue