Migrate pipelines - Best Effort
This commit is contained in:
parent
4e7cf06abf
commit
111f06ddf6
6 changed files with 620 additions and 5 deletions
|
|
@ -7,18 +7,24 @@ import (
|
|||
|
||||
"vix.ro/sonix/azure_migrate/internal/azure"
|
||||
"vix.ro/sonix/azure_migrate/internal/config"
|
||||
"vix.ro/sonix/azure_migrate/internal/converter"
|
||||
"vix.ro/sonix/azure_migrate/internal/forgejo"
|
||||
)
|
||||
|
||||
type AzureClient interface {
|
||||
GetRepositories() ([]azure.Repository, error)
|
||||
GetAuthenticatedURL(remoteURL, pat string) string
|
||||
GetBuildDefinitions(project string) ([]azure.BuildDefinition, error)
|
||||
GetPipelineVariables(project string, definitionID int) (map[string]string, error)
|
||||
GetPipelineContent(repoURL, branch, filePath string) (string, error)
|
||||
}
|
||||
|
||||
type ForgejoClient interface {
|
||||
GetUser(username string) (*forgejo.User, error)
|
||||
RepositoryExists(owner, name string) (bool, error)
|
||||
CreateMirror(req forgejo.MigrateRequest) (*forgejo.Repository, error)
|
||||
CreateFile(owner, repo, path, content, message string) error
|
||||
CreateSecret(owner, repo, name, data string) error
|
||||
}
|
||||
|
||||
type Migrator struct {
|
||||
|
|
@ -146,6 +152,12 @@ func (m *Migrator) Run() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) addError(err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.errors = append(m.errors, err)
|
||||
}
|
||||
|
||||
func (m *Migrator) migrateRepository(repo azure.Repository, uid int64) MigrationResult {
|
||||
result := MigrationResult{
|
||||
RepoName: repo.Name,
|
||||
|
|
@ -184,6 +196,9 @@ func (m *Migrator) migrateRepository(repo azure.Repository, uid int64) Migration
|
|||
if m.config.DryRun {
|
||||
result.Success = true
|
||||
result.ForgejoURL = fmt.Sprintf("%s/%s/%s", m.config.ForgejoURL, m.config.ForgejoOwner, repo.Name)
|
||||
|
||||
// Show what would be migrated for pipelines
|
||||
fmt.Printf(" [DRY-RUN] Would migrate pipeline for %s\n", repo.Name)
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -196,15 +211,135 @@ func (m *Migrator) migrateRepository(repo azure.Repository, uid int64) Migration
|
|||
return result
|
||||
}
|
||||
|
||||
// Migrate pipelines after repo creation
|
||||
if err := m.migratePipeline(repo, createdRepo); err != nil {
|
||||
// Log warning but don't fail the whole migration
|
||||
fmt.Printf(" Warning: Pipeline migration failed for %s: %v\n", repo.Name, err)
|
||||
}
|
||||
|
||||
result.Success = true
|
||||
result.ForgejoURL = createdRepo.HTMLURL
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Migrator) addError(err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.errors = append(m.errors, err)
|
||||
func (m *Migrator) migratePipeline(repo azure.Repository, forgejoRepo *forgejo.Repository) error {
|
||||
fmt.Printf(" Checking for pipelines in %s...\n", repo.Name)
|
||||
|
||||
// Get build definitions for this repo's project
|
||||
defs, err := m.azureClient.GetBuildDefinitions(repo.Project.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get pipeline definitions: %w", err)
|
||||
}
|
||||
|
||||
// Find pipelines associated with this repository
|
||||
for _, def := range defs {
|
||||
// Check if this pipeline belongs to the current repo
|
||||
if !strings.Contains(def.Repository.URL, repo.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" Found pipeline: %s (ID: %d)\n", def.Name, def.ID)
|
||||
|
||||
// Get variables
|
||||
vars, err := m.azureClient.GetPipelineVariables(repo.Project.Name, def.ID)
|
||||
if err != nil {
|
||||
fmt.Printf(" Warning: Could not get variables: %v\n", err)
|
||||
}
|
||||
|
||||
// Migrate variables as secrets
|
||||
for key, value := range vars {
|
||||
secretName := "AZURE_" + strings.ToUpper(key)
|
||||
if err := m.forgejoClient.CreateSecret(m.config.ForgejoOwner, repo.Name, secretName, value); err != nil {
|
||||
fmt.Printf(" Warning: Failed to create secret %s: %v\n", secretName, err)
|
||||
} else {
|
||||
fmt.Printf(" Migrated secret: %s\n", secretName)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get YAML content
|
||||
var yamlContent string
|
||||
if def.Process.YamlFilename != "" {
|
||||
// YAML pipeline
|
||||
content, err := m.azureClient.GetPipelineContent(repo.RemoteURL, "master", def.Process.YamlFilename)
|
||||
if err != nil {
|
||||
content, err = m.azureClient.GetPipelineContent(repo.RemoteURL, "main", def.Process.YamlFilename)
|
||||
}
|
||||
if err == nil {
|
||||
yamlContent = content
|
||||
}
|
||||
}
|
||||
|
||||
if yamlContent != "" {
|
||||
// Convert YAML
|
||||
conv := converter.New()
|
||||
forgejoYAML, err := conv.ConvertAzureToForgejo(yamlContent, repo.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert pipeline YAML: %w", err)
|
||||
}
|
||||
|
||||
// Create workflow file
|
||||
workflowPath := fmt.Sprintf(".forgejo/workflows/%s.yml", strings.ReplaceAll(def.Name, " ", "-"))
|
||||
// Also create .github/workflows for compatibility
|
||||
githubPath := fmt.Sprintf(".github/workflows/%s.yml", strings.ReplaceAll(def.Name, " ", "-"))
|
||||
|
||||
commitMsg := fmt.Sprintf("Migrate Azure Pipeline: %s", def.Name)
|
||||
|
||||
// Try Forgejo path first
|
||||
err = m.forgejoClient.CreateFile(m.config.ForgejoOwner, repo.Name, workflowPath, forgejoYAML, commitMsg)
|
||||
if err != nil {
|
||||
// If .forgejo doesn't work (older versions), try .github
|
||||
err = m.forgejoClient.CreateFile(m.config.ForgejoOwner, repo.Name, githubPath, forgejoYAML, commitMsg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create workflow file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf(" Created workflow: %s\n", workflowPath)
|
||||
|
||||
// Print conversion warnings
|
||||
for _, warning := range conv.Warnings {
|
||||
fmt.Printf(" ⚠️ %s\n", warning)
|
||||
}
|
||||
} else {
|
||||
// Classic pipeline (UI-based) - create placeholder
|
||||
placeholder := m.generatePlaceholderWorkflow(def)
|
||||
workflowPath := fmt.Sprintf(".forgejo/workflows/%s-placeholder.yml", def.ID)
|
||||
|
||||
err = m.forgejoClient.CreateFile(m.config.ForgejoOwner, repo.Name, workflowPath, placeholder,
|
||||
"Placeholder for classic Azure Pipeline")
|
||||
if err != nil {
|
||||
fmt.Printf(" Warning: Could not create placeholder: %v\n", err)
|
||||
}
|
||||
fmt.Printf(" Created placeholder for classic pipeline (manual conversion required)\n")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) generatePlaceholderWorkflow(def azure.BuildDefinition) string {
|
||||
return fmt.Sprintf(`# Placeholder for Classic Azure Pipeline: %s
|
||||
# Pipeline ID: %d
|
||||
#
|
||||
# This pipeline was defined in Azure DevOps UI (Classic) and cannot be automatically converted.
|
||||
# Please manually recreate this workflow using Forgejo Actions syntax.
|
||||
#
|
||||
# Variables defined in this pipeline:
|
||||
name: Azure Pipeline Placeholder - %s
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: TODO - Add steps from Azure Pipeline
|
||||
run: |
|
||||
echo "Original Azure Pipeline: %s"
|
||||
echo "Review the Azure DevOps UI for this pipeline's tasks and recreate them here"
|
||||
`, def.Name, def.ID, def.Name, def.Name)
|
||||
}
|
||||
|
||||
func truncate(s string, length int) string {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue