platformimport

package
v0.9.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package platformimport reads apps from another self-hosted platform (read-only) into a neutral model and maps it to this platform's resources. The source credential is used in memory only.

Index

Constants

View Source
const (
	EmitStackFile     = "stack.yaml"
	EmitProjectFile   = "project.yaml"
	EmitDatabasesFile = "databases.yaml"
	EmitAppsFile      = "apps.yaml"
	EmitDomainsFile   = "domains.yaml"
	EmitSecretsFile   = "SECRETS.md"
	EmitNotesFile     = "MIGRATION.md"
)

Emitted file names, stable so a migration PR diffs cleanly.

View Source
const (
	AllowPrivateEnv  = "APP_IMPORT_ALLOW_PRIVATE_NETWORKS"
	AllowLoopbackEnv = "APP_IMPORT_ALLOW_LOOPBACK"
)

Environment variables that must be set before a request may opt in to private or loopback source addresses. Both the env var and the request field are required, so a leaked API token alone cannot aim the control plane at the internal network.

View Source
const (
	LabelSourceID       = "import/source-id"
	LabelSourcePlatform = "import/source-platform"
	LabelSourceName     = "import/source-name"
	// LabelSession ties a staged app to the guided import session that created it.
	LabelSession = "import/session"
)

Labels stamped on every imported resource. The source-id label is what makes a re-run idempotent and lets an operator find imports to delete.

View Source
const (
	StatusMapped         = "mapped"
	StatusNeedsAttention = "needs-attention"
	StatusUnsupported    = "unsupported"
	StatusAlready        = "already-imported"
	StatusSkipped        = "skipped"
	StatusCreated        = "created"
	StatusFailed         = "failed"
)

Item statuses in a Report.

View Source
const (
	CollisionSuffix = "suffix"
	CollisionSkip   = "skip"
)

Collision modes for a target name that is already taken.

View Source
const ConnectionHint = "database connection string, created empty here: set it after the database exists, or run databases link"

ConnectionHint is the SECRETS.md note for a variable that carries a database connection string.

View Source
const DataNote = "" /* 171-byte string literal not displayed */

DataNote is added to every report that contains databases.

View Source
const DefaultHerokuURL = "https://api.heroku.com"

DefaultHerokuURL is the public Heroku Platform API.

View Source
const DefaultRenderURL = "https://api.render.com/v1"

DefaultRenderURL is the public Render REST API.

View Source
const DefaultVercelURL = "https://api.vercel.com"

DefaultVercelURL is the public Vercel REST API.

View Source
const (

	// MaxPages bounds every paginated or fan-out walk.
	MaxPages = 200
)
View Source
const SecretMask = "********"

SecretMask replaces a secret value stored in a Service.

Variables

View Source
var ErrBlockedAddress = errors.New("platformimport: source address is not allowed")

ErrBlockedAddress is returned when the source URL resolves to an address the network policy forbids.

Functions

func LooksSecret

func LooksSecret(key string) bool

LooksSecret reports whether an env var name suggests a secret value.

func NormalizeSchedule

func NormalizeSchedule(s string) string

NormalizeSchedule expands the named frequencies a source accepts into a cron expression.

func ParseDBVersions

func ParseDBVersions(raw string) (map[string]string, error)

ParseDBVersions reads "postgres=16,redis=7" into an engine to version map.

func SanitizeJobName

func SanitizeJobName(s string) string

SanitizeJobName turns a source task name into a valid scheduled job name.

func SanitizeName

func SanitizeName(s string) string

SanitizeName turns a source name into a valid resource name.

func ValidateVercelTeam

func ValidateVercelTeam(s string) (string, error)

ValidateVercelTeam trims and checks a team id or slug before it reaches a query string.

Types

type APIError

type APIError struct {
	Status int
	Body   string
}

APIError is a non-2xx reply from the source.

func (*APIError) Error

func (e *APIError) Error() string

type App

type App struct {
	SourceID    string
	Name        string
	Project     string
	Environment string
	// Server names the source host the app runs on, empty when unreported.
	Server string
	// BuildPack is the source's own build method name, unmapped.
	BuildPack string
	// PrivateRepo is true when the source needs credentials to clone.
	PrivateRepo bool
	Kind        SourceKind
	Image       string
	// ImageID is the source host's local image ID, set by snapshot sources.
	ImageID string
	// SourceContainer is the running container's ID on the source host, set by live Docker sources.
	SourceContainer string
	// HostBuilt marks an image built on the source host and absent from any registry.
	HostBuilt   bool
	GitURL      string
	GitBranch   string
	BuildMethod string
	BuildPath   string
	Port        int
	Env         []Env
	Domains     []string
	Volumes     []Volume
	Replicas    int
	Health      *HealthCheck
	Crons       []Cron
	MemoryBytes int64
	NanoCPUs    int64
	Notes       []Note
}

App is one application in the neutral model.

type AppPlan

type AppPlan struct {
	SourceID    string            `json:"source_id"`
	SourceName  string            `json:"source_name"`
	Name        string            `json:"name"`
	Image       string            `json:"image"`
	Port        int               `json:"port"`
	Env         map[string]string `json:"env,omitempty"`
	Secrets     map[string]string `json:"-"`
	Domains     []string          `json:"domains,omitempty"`
	Volumes     []VolumePlan      `json:"volumes,omitempty"`
	Replicas    int               `json:"replicas"`
	Health      *HealthPlan       `json:"health,omitempty"`
	MemoryBytes int64             `json:"memory_bytes,omitempty"`
	NanoCPUs    int64             `json:"nano_cpus,omitempty"`
	Labels      map[string]string `json:"labels"`
	Project     string            `json:"project,omitempty"`
	GitURL      string            `json:"git_url,omitempty"`
	GitBranch   string            `json:"git_branch,omitempty"`
	BuildMethod string            `json:"build_method,omitempty"`
	BuildPath   string            `json:"build_path,omitempty"`
	Crons       []CronPlan        `json:"crons,omitempty"`
	// UnreadSecrets names secret variables the source would not reveal.
	UnreadSecrets []string `json:"-"`
}

AppPlan is one app create request. Secrets holds plaintext and is never serialized.

type Applier

type Applier interface {
	CreateApp(ctx context.Context, p AppPlan) (warnings []string, err error)
	CreateDatabase(ctx context.Context, p DatabasePlan) (warnings []string, err error)
}

Applier creates resources on this platform. Implementations must be safe to call again after a partial failure.

type BackupSchedule

type BackupSchedule struct {
	Schedule string
	Enabled  bool
	// Remote is true when the source also uploads the backup to S3.
	Remote bool
}

BackupSchedule is one scheduled database backup on the source.

type CapRoverSource

type CapRoverSource struct {
	// contains filtered or unexported fields
}

CapRoverSource reads a CapRover instance. The login POST is the only non-GET call it makes.

func NewCapRover

func NewCapRover(baseURL, password string, o ClientOptions) (*CapRoverSource, error)

NewCapRover builds a CapRover source. The password is exchanged for a session token in memory and never stored.

func (*CapRoverSource) Discover

func (s *CapRoverSource) Discover(ctx context.Context) (*Discovery, error)

Discover logs in and reads every app definition.

type ClientOptions

type ClientOptions struct {
	Policy   NetworkPolicy
	Insecure bool
	// Team scopes a Vercel source to one team id or slug, other platforms reject it.
	Team string
}

ClientOptions configure the source HTTP client.

type ComposeSource

type ComposeSource struct {
	Data []byte
}

ComposeSource maps a plain compose file, one app per service.

func (ComposeSource) Discover

func (s ComposeSource) Discover(_ context.Context) (*Discovery, error)

Discover parses the compose file. Services with build: blocks are reported as unsupported since there is no source tree to build from.

type CoolifySource

type CoolifySource struct {
	// contains filtered or unexported fields
}

CoolifySource reads a Coolify instance through its bearer-token API v1.

func NewCoolify

func NewCoolify(baseURL, token string, o ClientOptions) (*CoolifySource, error)

NewCoolify builds a Coolify source. The token is kept in memory only.

func (*CoolifySource) Discover

func (c *CoolifySource) Discover(ctx context.Context) (*Discovery, error)

Discover walks projects, environments, applications, databases and services.

type Cron

type Cron struct {
	Name     string
	Schedule string
	Command  string
}

Cron is a scheduled command.

type CronPlan

type CronPlan struct {
	Name     string `json:"name"`
	Schedule string `json:"schedule"`
	Command  string `json:"command"`
}

CronPlan is a scheduled command that becomes a scheduled job on the new app.

type Database

type Database struct {
	SourceID string
	Name     string
	Project  string
	Engine   string
	Version  string
	// Environment and InternalHost are set by sources that give each
	// database an in-network hostname apps connect to.
	Environment  string
	InternalHost string
	// Server, User, DBName, Public and PublicPort describe how the source
	// exposes the database. No credential is ever read here.
	Server     string
	User       string
	DBName     string
	Public     bool
	PublicPort int
	Backups    []BackupSchedule
}

Database is a managed database in the source platform.

type DatabasePlan

type DatabasePlan struct {
	SourceID   string `json:"source_id"`
	SourceName string `json:"source_name"`
	Name       string `json:"name"`
	Engine     string `json:"engine"`
	Version    string `json:"version"`
	Project    string `json:"project,omitempty"`
}

DatabasePlan is one managed database create request (empty, no data).

type Discovery

type Discovery struct {
	Platform    Platform
	Projects    []string
	Apps        []App
	Databases   []Database
	Unsupported []Unsupported
	// Services holds each one-click service's masked compose body and env.
	Services []Service
	Servers  []Server
	// Warnings are source reads that failed softly, so the plan lacks that detail.
	Warnings []string
}

Discovery is everything read from a source.

type DockerSSHSource

type DockerSSHSource struct {
	Reader InspectReader
}

DockerSSHSource reads a host's containers through a read-only Docker socket tunnel and maps them like a pasted docker inspect snapshot. Env comes from each container's runtime config; the old platform's own stored config (Coolify's database, CapRover's /captain/data, Dokku's /home/dokku) is not read.

func (DockerSSHSource) Discover

func (s DockerSSHSource) Discover(ctx context.Context) (*Discovery, error)

Discover reads every container over the tunnel and parses the result.

type DockerSource

type DockerSource struct {
	Data []byte
}

DockerSource maps a `docker inspect` snapshot of every container on a host into the neutral model. It is read-only and needs no credential for the platform that launched the containers, so it works when only the Docker host is reachable (no Coolify token, no Dokploy key).

func (DockerSource) Discover

func (s DockerSource) Discover(_ context.Context) (*Discovery, error)

Discover parses the snapshot, a JSON array as printed by `docker inspect $(docker ps -aq)`.

type DokkuEnvSource

type DokkuEnvSource struct {
	AppName string
	Data    string
}

DokkuEnvSource maps one Dokku app from a `dokku config:export` style dump (KEY=VALUE lines). It never contacts a Dokku host.

func (DokkuEnvSource) Discover

func (s DokkuEnvSource) Discover(_ context.Context) (*Discovery, error)

Discover returns one app with the dumped variables. Dokku's own generated variables (DOKKU_*, GIT_REV) are dropped.

type DokploySource

type DokploySource struct {
	// contains filtered or unexported fields
}

DokploySource reads a Dokploy instance through its x-api-key API.

func NewDokploy

func NewDokploy(baseURL, apiKey string, o ClientOptions) (*DokploySource, error)

NewDokploy builds a Dokploy source. The API key is kept in memory only.

func (*DokploySource) Discover

func (s *DokploySource) Discover(ctx context.Context) (*Discovery, error)

Discover lists projects (which embed their resources) and reads each app.

type EmitApp

type EmitApp struct {
	Name string
	// Image is the prebuilt image. Empty means use EmitOptions.ImageRepo.
	Image       string
	Port        int
	Replicas    int
	Env         map[string]string
	Secrets     []EmitSecret
	Domains     []string
	MemoryBytes int64
	// Memory is a ready "512Mi" string and wins over MemoryBytes.
	Memory     string
	NanoCPUs   int64
	HealthPath string
	Command    []string
	PreDeploy  string
	PostDeploy string
	// Connections maps an env var to the emitted database it reads from.
	Connections map[string]EmitConnection
	Jobs        []EmitJob
	// Notes are follow-ups for MIGRATION.md that the documents cannot express.
	Notes []string
}

EmitApp is one app to write as a kind: App document.

type EmitConnection

type EmitConnection struct {
	Database string
	Field    string
}

EmitConnection is a { from: { database, field } } env reference.

type EmitDatabase

type EmitDatabase struct {
	Name    string
	Engine  string
	Version string
}

EmitDatabase is one kind: Database document.

type EmitFile

type EmitFile struct {
	Name    string
	Content []byte
}

EmitFile is one file of the emitted stack directory.

func EmitStack

func EmitStack(in EmitInput, o EmitOptions) ([]EmitFile, error)

EmitStack renders the stack directory: Stack, Project, Database, App and Domain documents plus SECRETS.md (names only) and MIGRATION.md. Output is sorted so the same input always gives the same bytes.

type EmitInput

type EmitInput struct {
	// Source names where the data came from, for the checklist header.
	Source    string
	Apps      []EmitApp
	Databases []EmitDatabase
	// Notes are source-wide follow-ups for MIGRATION.md.
	Notes []string
}

EmitInput is everything an emit needs, from any source adapter.

type EmitJob

type EmitJob struct {
	Name     string
	Schedule string
	Command  []string
	// Mode is "exec" to run in the app's running container; empty means a fresh one.
	Mode     string
	Timezone string
}

EmitJob is one scheduled job of an App document.

type EmitOptions

type EmitOptions struct {
	Stack       string
	Project     string
	Environment string
	// ImageRepo prefixes the image of an app that has none, as REPO/APP:latest.
	ImageRepo string
}

EmitOptions name the stack and fill what the source does not know.

type EmitSecret

type EmitSecret struct {
	Name string
	Hint string
}

EmitSecret is a secret the emitted documents reference by name. The value is never held here.

type Env

type Env struct {
	Key    string
	Value  string
	Secret bool
	// BuildTime marks a variable the source only exposes during the image build.
	BuildTime bool
}

Env is one environment variable. Secret is true when the source marked it secret or the key looks like one.

type HealthCheck

type HealthCheck struct {
	Path            string
	IntervalSeconds int
	TimeoutSeconds  int
	Retries         int
}

HealthCheck is an HTTP probe.

type HealthPlan

type HealthPlan struct {
	Path            string
	IntervalSeconds int
	TimeoutSeconds  int
	Failures        int
}

HealthPlan is an HTTP readiness probe.

type HerokuSource

type HerokuSource struct {
	// contains filtered or unexported fields
}

HerokuSource reads a Heroku account through the Platform API, GET only.

func NewHeroku

func NewHeroku(baseURL, token string, o ClientOptions) (*HerokuSource, error)

NewHeroku builds a Heroku source. The API token is kept in memory only. An empty baseURL means the public API.

func (*HerokuSource) Discover

func (s *HerokuSource) Discover(ctx context.Context) (*Discovery, error)

Discover lists apps, then reads config, formation, domains and add-ons for each.

type InspectReader

type InspectReader interface {
	InspectAll(ctx context.Context) ([]byte, error)
}

InspectReader returns the inspect JSON of every container on a host as one array.

type Item

type Item struct {
	Kind       string   `json:"kind"`
	SourceID   string   `json:"source_id"`
	SourceName string   `json:"source_name"`
	Target     string   `json:"target,omitempty"`
	Status     string   `json:"status"`
	Reasons    []string `json:"reasons,omitempty"`
	Manual     []string `json:"manual,omitempty"`
}

Item is one report row.

type NetworkPolicy

type NetworkPolicy struct {
	AllowPrivate  bool
	AllowLoopback bool
}

NetworkPolicy decides which destinations the source client may dial. Cloud metadata and link-local addresses are never allowed.

func (NetworkPolicy) Check

func (p NetworkPolicy) Check(addr netip.Addr) error

Check reports whether addr may be dialed under the policy.

type Note

type Note struct {
	// Area groups notes in the exit report, such as "webhook" or "command". Empty means general.
	Area   string
	Reason string
	Manual string
}

Note is a per-item caveat found while reading the source.

type Plan

type Plan struct {
	Apps      []AppPlan
	Databases []DatabasePlan
	Report    Report
}

Plan is the mapped result of a Discovery.

func BuildPlan

func BuildPlan(d *Discovery, o PlanOptions) *Plan

BuildPlan maps a Discovery onto create requests and a Report.

func (*Plan) EmitInput

func (p *Plan) EmitInput() EmitInput

EmitInput maps a Plan (any source adapter) to the neutral emit model. Secret values in the plan are dropped here, only their names go on.

type PlanOptions

type PlanOptions struct {
	// Only restricts the plan to these source ids or source names. Empty means all.
	Only []string
	// Collision is CollisionSuffix (default) or CollisionSkip.
	Collision string
	// ExistingApps maps an import label value (platform:sourceID) to the
	// app already created for it. TakenApps is every app name in use.
	ExistingApps map[string]string
	TakenApps    map[string]bool
	// ExistingDatabases maps a database name to "engine:version".
	ExistingDatabases map[string]string
	// DBVersions is the engine version the user chose per engine, and
	// LatestDBVersions the newest catalog version per engine. Both fill in
	// databases whose source reports no version.
	DBVersions       map[string]string
	LatestDBVersions map[string]string
}

PlanOptions steer mapping.

type Platform

type Platform string

Platform identifies a supported source.

const (
	Coolify  Platform = "coolify"
	Dokploy  Platform = "dokploy"
	CapRover Platform = "caprover"
	Heroku   Platform = "heroku"
	Vercel   Platform = "vercel"
	Render   Platform = "render"
	Compose  Platform = "compose"
	Dokku    Platform = "dokku"
	// Docker is a docker inspect snapshot of one host, no platform API.
	Docker Platform = "docker"
	// DockerSSH reads one host's Docker socket through an SSH login, no platform API.
	DockerSSH Platform = "docker-ssh"
)

Supported source platforms.

func ParsePlatform

func ParsePlatform(s string) (Platform, error)

ParsePlatform validates a platform name: a live source or a pasted file.

type RenderSource

type RenderSource struct {
	// contains filtered or unexported fields
}

RenderSource reads a Render workspace through the REST API, GET only.

func NewRender

func NewRender(baseURL, token string, o ClientOptions) (*RenderSource, error)

NewRender builds a Render source. The API key is kept in memory only. An empty baseURL means the public API.

func (*RenderSource) Discover

func (s *RenderSource) Discover(ctx context.Context) (*Discovery, error)

Discover lists services, then reads env vars and domains for each, then the datastores.

type Report

type Report struct {
	Platform Platform       `json:"platform"`
	Items    []Item         `json:"items"`
	Counts   map[string]int `json:"counts"`
	Notes    []string       `json:"notes,omitempty"`
}

Report is the per-item outcome of a discover, plan or apply.

func Apply

func Apply(ctx context.Context, plan *Plan, ap Applier) Report

Apply creates every planned resource, continuing past individual failures so a re-run (which skips what already exists) can finish the rest. It returns the plan's report with created and failed statuses.

type Server

type Server struct {
	Name    string
	Address string
	// Proxy names the reverse proxy the source runs there, empty when none.
	Proxy string
	// BuildServer is true when the source uses the host only to build images.
	BuildServer bool
	Reachable   bool
}

Server is a host the source platform deploys to.

type Service

type Service struct {
	SourceID    string
	Name        string
	Project     string
	Environment string
	Server      string
	Type        string
	Compose     string
	Env         []Env
}

Service is a one-click service stack read from the source. Compose is the stack's compose body with inline secret values masked; Env values of secret keys are masked too. Both are empty when the source read failed softly.

type Source

type Source interface {
	Discover(ctx context.Context) (*Discovery, error)
}

Source discovers the neutral model from one source platform.

func NewSource

func NewSource(platform Platform, baseURL, token string, o ClientOptions) (Source, error)

NewSource builds a live source adapter for a platform. The token is the Coolify bearer token, Dokploy API key, CapRover password Heroku API token, Vercel token or Render API key.

type SourceKind

type SourceKind string

SourceKind says where an app's code comes from.

const (
	SourceImage   SourceKind = "image"
	SourceGit     SourceKind = "git"
	SourceUnknown SourceKind = "unknown"
)

Where an app comes from.

type Unsupported

type Unsupported struct {
	Kind     string
	SourceID string
	Name     string
	Reason   string
	Manual   string
	// Context the source reported about the unsupported item, for display.
	Project     string
	Environment string
	Server      string
	Source      string
	Domains     []string
	Port        int
}

Unsupported is something that cannot be imported.

type VercelSource

type VercelSource struct {

	// TeamID scopes every call to a team, empty means the token's personal account.
	TeamID string
	// contains filtered or unexported fields
}

VercelSource reads a Vercel account or team through the REST API, GET only.

func NewVercel

func NewVercel(baseURL, token string, o ClientOptions) (*VercelSource, error)

NewVercel builds a Vercel source. The API token is kept in memory only. An empty baseURL means the public API.

func (*VercelSource) Discover

func (s *VercelSource) Discover(ctx context.Context) (*Discovery, error)

Discover lists projects, then reads env keys and domains for each.

func (*VercelSource) WithTeam

func (s *VercelSource) WithTeam(id string) *VercelSource

WithTeam scopes the source to one team id (team_...).

type Volume

type Volume struct {
	Name          string
	HostPath      string
	ContainerPath string
	ReadOnly      bool
	// SizeBytes is 0 when the source API does not report volume sizes.
	SizeBytes int64
}

Volume is a persistent mount.

type VolumePlan

type VolumePlan struct {
	Name          string
	HostPath      string
	ContainerPath string
	ReadOnly      bool
}

VolumePlan is a named volume or bind mount.

Directories

Path Synopsis
Package fakecoolify is a read-only stand-in for the Coolify v4 REST API, shaped from the documented responses the importer consumes.
Package fakecoolify is a read-only stand-in for the Coolify v4 REST API, shaped from the documented responses the importer consumes.
cmd/fakecoolify command
Command fakecoolify serves the read-only fake Coolify API for local runs of the app import flow.
Command fakecoolify serves the read-only fake Coolify API for local runs of the app import flow.
Package fakeheroku is a read-only stand-in for the Heroku Platform API, shaped from the documented responses the importer consumes.
Package fakeheroku is a read-only stand-in for the Heroku Platform API, shaped from the documented responses the importer consumes.
Package fakerender is a read-only stand-in for the Render REST API, shaped from the documented responses the importer consumes.
Package fakerender is a read-only stand-in for the Render REST API, shaped from the documented responses the importer consumes.
Package fakevercel is a read-only stand-in for the Vercel REST API, shaped from the documented responses the importer consumes.
Package fakevercel is a read-only stand-in for the Vercel REST API, shaped from the documented responses the importer consumes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL