Skip to content

[WIP] Add ClonSun Integration #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions commands/clone_project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package commands

import (
"fmt"

"github.com/platformsh/cli/internal/config"
"github.com/spf13/cobra"
"github.com/upsun/clonsun/api"
"github.com/upsun/lib-sun/entity"
utils "github.com/upsun/lib-sun/utility"
orderedmap "github.com/wk8/go-ordered-map/v2"
)

func innerCloneProjectCommand(cnf *config.Config) Command {
noInteractionOption := NoInteractionOption(cnf)

return Command{
Name: CommandName{
Namespace: "project",
Command: "clone",
},
Usage: []string{
cnf.Application.Executable + " project:clone",
},
Aliases: []string{
"convert",
},
Description: "Clone an existing Platform.sh project environment to a different region and/or to Upsun. Cloning will import and push your codebase, import & export project/environment configurations, import & export your sql databases, import & export your media files. If you selected to clone a project to Upsun the required config.yaml will be automatically generated and added to your project.",
Help: "",
Copy link
Preview

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty Help field should contain detailed help documentation for the clone command.

Copilot uses AI. Check for mistakes.

Examples: []Example{
{
Commandline: "",
Description: "Clone an existing Platform.sh project environment to a different region and/or to Upsun. Cloning will import and push your codebase, import & export project/environment configurations, import & export your sql databases, import & export your media files. If you selected to clone a project to Upsun the required config.yaml will be automatically generated and added to your project.",
Copy link
Preview

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty commandline example should be populated with actual usage examples to help users understand how to use the command.

Suggested change
Description: "Clone an existing Platform.sh project environment to a different region and/or to Upsun. Cloning will import and push your codebase, import & export project/environment configurations, import & export your sql databases, import & export your media files. If you selected to clone a project to Upsun the required config.yaml will be automatically generated and added to your project.",
Commandline: cnf.Application.Executable + " project:clone --from <source-project-id> --to <target-project-id> --region <target-region>",
Description: "Clone an existing Platform.sh project environment to a different region and/or to Upsun. Replace <source-project-id>, <target-project-id>, and <target-region> with your specific values.",

Copilot uses AI. Check for mistakes.

},
},
Definition: Definition{
Arguments: &orderedmap.OrderedMap[string, Argument]{},
Options: orderedmap.New[string, Option](orderedmap.WithInitialData[string, Option](
orderedmap.Pair[string, Option]{
Key: HelpOption.GetName(),
Value: HelpOption,
},
orderedmap.Pair[string, Option]{
Key: VerboseOption.GetName(),
Value: VerboseOption,
},
orderedmap.Pair[string, Option]{
Key: VersionOption.GetName(),
Value: VersionOption,
},
orderedmap.Pair[string, Option]{
Key: YesOption.GetName(),
Value: YesOption,
},
orderedmap.Pair[string, Option]{
Key: noInteractionOption.GetName(),
Value: noInteractionOption,
},
)),
},
Hidden: false,
}
}

func NewCloneProjectCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "project:clone",
Short: "Clone the current project.",
Aliases: []string{"clone"},
//Args: cobra.ExactArgs(1),
RunE: runCloneProject,
}

return cmd
}

func runCloneProject(cmd *cobra.Command, args []string) error {
//TODO: Dynamically from console arguments
projectFrom := entity.MakeProjectContext(
"platform",
"zrkopp7gsvzwa",
Copy link
Preview

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded project ID should be replaced with dynamic input from command arguments. This makes the command unusable for other projects.

Suggested change
"zrkopp7gsvzwa",
if len(args) < 1 {
return fmt.Errorf("missing required project ID argument")
}
projectID := args[0]
projectFrom := entity.MakeProjectContext(
"platform",
projectID,

Copilot uses AI. Check for mistakes.

Copy link
Preview

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented out argument validation should either be implemented or removed. The command currently accepts any number of arguments without validation.

Suggested change
"zrkopp7gsvzwa",
Use: "project:clone [PROJECT_ID]",
Short: "Clone the current project.",
Aliases: []string{"clone"},
Args: cobra.ExactArgs(1),
RunE: runCloneProject,
}
return cmd
}
func runCloneProject(cmd *cobra.Command, args []string) error {
// Use the provided argument for the source project ID
projectID := args[0]
projectFrom := entity.MakeProjectContext(
"platform",
projectID,

Copilot uses AI. Check for mistakes.

"master",
)

//TODO: Dynamically from console arguments
projectTo := entity.MakeProjectContext(
"upsun",
"",
"master",
)
projectTo.Region = "eu-3.platform.sh"
projectTo.OrgEmail = "Mick"
Copy link
Preview

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded email value "Mick" should be replaced with dynamic input. This appears to be test data left in production code.

Suggested change
projectTo.OrgEmail = "Mick"
projectTo.OrgEmail = orgEmail

Copilot uses AI. Check for mistakes.


//TODO: Need on this command ?
Copy link
Preview

Copilot AI Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded project values should be replaced with dynamic input from command arguments. This makes the command non-functional for real usage.

Suggested change
//TODO: Need on this command ?
Short: "Clone a project from one provider to another.",
Aliases: []string{"clone"},
RunE: runCloneProject,
}
// Add flags for source project
cmd.Flags().String("from-provider", "", "Source provider (e.g. platform)")
cmd.Flags().String("from-project-id", "", "Source project ID")
cmd.Flags().String("from-branch", "master", "Source branch")
// Add flags for destination project
cmd.Flags().String("to-provider", "", "Destination provider (e.g. upsun)")
cmd.Flags().String("to-project-id", "", "Destination project ID (optional)")
cmd.Flags().String("to-branch", "master", "Destination branch")
cmd.Flags().String("to-region", "", "Destination region (e.g. eu-3.platform.sh)")
cmd.Flags().String("to-org-email", "", "Destination org email")
cmd.MarkFlagRequired("from-provider")
cmd.MarkFlagRequired("from-project-id")
cmd.MarkFlagRequired("to-provider")
cmd.MarkFlagRequired("to-region")
cmd.MarkFlagRequired("to-org-email")
return cmd
}
func runCloneProject(cmd *cobra.Command, args []string) error {
// Get source project flags
fromProvider, _ := cmd.Flags().GetString("from-provider")
fromProjectID, _ := cmd.Flags().GetString("from-project-id")
fromBranch, _ := cmd.Flags().GetString("from-branch")
// Get destination project flags
toProvider, _ := cmd.Flags().GetString("to-provider")
toProjectID, _ := cmd.Flags().GetString("to-project-id")
toBranch, _ := cmd.Flags().GetString("to-branch")
toRegion, _ := cmd.Flags().GetString("to-region")
toOrgEmail, _ := cmd.Flags().GetString("to-org-email")
projectFrom := entity.MakeProjectContext(
fromProvider,
fromProjectID,
fromBranch,
)
projectTo := entity.MakeProjectContext(
toProvider,
toProjectID,
toBranch,
)
projectTo.Region = toRegion
projectTo.OrgEmail = toOrgEmail

Copilot uses AI. Check for mistakes.

if !utils.IsAuthenticated(projectFrom) {
fmt.Printf("You are not authenticated, please run: %v login\n", projectFrom.Provider)
}
if !utils.IsAuthenticated(projectTo) {
fmt.Printf("You are not authenticated, please run: %v login\n", projectTo.Provider)
}

api.Clone(projectFrom, projectTo)

return nil
}
8 changes: 8 additions & 0 deletions commands/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ func newListCommand(cnf *config.Config) *cobra.Command {
list.AddCommand(&appConfigValidateCommand)
}

// Add ClonSun to command list
appCloneProjectCommand := innerCloneProjectCommand(cnf)

if !list.DescribesNamespace() || list.Namespace == appCloneProjectCommand.Name.Namespace {
list.AddCommand(&appCloneProjectCommand)
}
// End of ClonSun

format := viper.GetString("format")
raw := viper.GetBool("raw")

Expand Down
9 changes: 9 additions & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob
fmt.Println(internalCmd.HelpPage(cnf))
})

// Add ClonSun to command
cloneCommand := NewCloneProjectCommand()
cloneCommand.SetHelpFunc(func(_ *cobra.Command, _ []string) {
internalCmd := innerCloneProjectCommand(cnf)
fmt.Println(internalCmd.HelpPage(cnf))
})
// End of ClonSun

// Add subcommands.
cmd.AddCommand(
newConfigInstallCommand(),
Expand All @@ -152,6 +160,7 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob
projectInitCmd,
validateCmd,
versionCommand,
cloneCommand,
)

//nolint:errcheck
Expand Down
8 changes: 6 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/platformsh/cli

go 1.23.0
go 1.23.5

toolchain go1.24.2
toolchain go1.24.4

require (
github.com/Masterminds/semver/v3 v3.3.1
Expand All @@ -16,6 +16,7 @@ require (
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.10.0
github.com/symfony-cli/terminal v1.0.7
github.com/upsun/clonsun v0.4.7
github.com/wk8/go-ordered-map/v2 v2.1.8
golang.org/x/crypto v0.37.0
golang.org/x/sync v0.13.0
Expand All @@ -32,6 +33,7 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/getsentry/sentry-go v0.28.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
Expand All @@ -57,6 +59,8 @@ require (
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/upsun/convsun v0.3.4 // indirect
github.com/upsun/lib-sun v0.3.15 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
Expand Down Expand Up @@ -88,6 +92,8 @@ github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNs
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/platformsh/platformify v0.4.0 h1:OMWjSsM8RwZN9lZuRdQD/URicy+OyKhNLMrca1JBlNg=
Expand Down Expand Up @@ -126,6 +132,12 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/symfony-cli/terminal v1.0.7 h1:57L9PUTE2cHfQtP8Ti8dyiiPEYlQ1NBIDpMJ3RPEGPc=
github.com/symfony-cli/terminal v1.0.7/go.mod h1:Etv22IyeGiMoIQPPj51hX31j7xuYl1njyuAFkrvybqU=
github.com/upsun/clonsun v0.4.7 h1:rQU8d7urrXi4geh4gRQqSO9VahSvMUqTIs/+cRwqlgk=
github.com/upsun/clonsun v0.4.7/go.mod h1:3q1JquMLq2XTN80RbLqBCosuGdn1d8mQYF9sFitXCT4=
github.com/upsun/convsun v0.3.4 h1:MiOidA/g2qRBe9h7iwiCotP+llF9W50maxzcR4Zw+hM=
github.com/upsun/convsun v0.3.4/go.mod h1:32MjtKrPJkXhgYomhm7B+Wkgpq9vKTX6NQox7ut2U8Q=
github.com/upsun/lib-sun v0.3.15 h1:PHxQ46vbRVWqJ5VUMsXzIFBj2L/hyCrsQUx49yS5MtI=
github.com/upsun/lib-sun v0.3.15/go.mod h1:LRs+1TttogDkbvpnz2sxcAPdePld1BuxSjRUs4fW4UM=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
Expand Down