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

appProjectConvertCommand := innerProjectConvertCommand(cnf)

if cnf.Service.ProjectConfigFlavor == "upsun" &&
(!list.DescribesNamespace() || list.Namespace == appProjectConvertCommand.Name.Namespace) {
list.AddCommand(&appProjectConvertCommand)
}

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

Expand Down
189 changes: 189 additions & 0 deletions commands/project_convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package commands

import (
"fmt"
"io"
"log"
"os"
"path/filepath"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/symfony-cli/terminal"
"github.com/upsun/lib-sun/detector"
"github.com/upsun/lib-sun/entity"
"github.com/upsun/lib-sun/readers"
utils "github.com/upsun/lib-sun/utility"
"github.com/upsun/lib-sun/writers"
orderedmap "github.com/wk8/go-ordered-map/v2"

"github.com/platformsh/cli/internal/config"
)

// innerProjectConvertCommand returns the Command struct for the convert config command.
func innerProjectConvertCommand(cnf *config.Config) Command {
noInteractionOption := NoInteractionOption(cnf)
providerOption := Option{
Name: "--provider",
Shortcut: "-p",
IsValueRequired: false,
Default: Any{"platformsh"},
Description: "The provider from which to convert the configuration. Currently, only 'platformsh' is supported.",
}

return Command{
Name: CommandName{
Namespace: "project",
Command: "convert",
},
Usage: []string{
cnf.Application.Executable + " convert",
},
Aliases: []string{
"convert",
},
Description: "Generate an Upsun compatible configuration based on the configuration from another provider.",
Help: "",
Examples: []Example{
{
Commandline: "--provider=platformsh",
Description: "Convert the Platform.sh project configuration files in your current directory",
},
},
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,
},
orderedmap.Pair[string, Option]{
Key: "provider",
Value: providerOption,
},
)),
},
Hidden: false,
}
}

// newProjectConvertCommand creates the cobra command for converting config.
func newProjectConvertCommand(cnf *config.Config) *cobra.Command {
cmd := &cobra.Command{
Use: "project:convert",
Short: "Generate locally Upsun configuration from another provider",
Aliases: []string{"convert"},
RunE: runProjectConvert,
}

cmd.Flags().StringP(
"provider",
"p",
"platformsh",
"The provider from which to convert the configuration. Currently, only 'platformsh' is supported.",
)

_ = viper.BindPFlag("provider", cmd.Flags().Lookup("provider"))
cmd.SetHelpFunc(func(_ *cobra.Command, _ []string) {
internalCmd := innerProjectConvertCommand(cnf)
fmt.Println(internalCmd.HelpPage(cnf))
})
return cmd
}

// runProjectConvert is the entry point for the convert config command.
func runProjectConvert(cmd *cobra.Command, _ []string) error {
if viper.GetString("provider") != "platformsh" {
return fmt.Errorf("only the 'platformsh' provider is currently supported")
}
return runPlatformShConvert(cmd)
}

// runPlatformShConvert performs the conversion from Platform.sh config to Upsun config.
func runPlatformShConvert(cmd *cobra.Command) error {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("could not get current working directory: %w", err)
}

cwd, err = filepath.Abs(filepath.Clean(cwd))
if err != nil {
return fmt.Errorf("could not normalize project workspace path: %w", err)
}

// Disable log for lib-sun
log.Default().SetOutput(io.Discard)

// Find config files
configFiles, err := detector.FindConfig(cwd)
if err != nil {
return fmt.Errorf("could not detect configuration files: %w", err)
}

// Read PSH application config files
var metaConfig entity.MetaConfig
readers.ReadApplications(&metaConfig, configFiles[entity.PSH_APPLICATION], cwd)
readers.ReadPlatforms(&metaConfig, configFiles[entity.PSH_PLATFORM], cwd)
if metaConfig.Applications.IsZero() {
return fmt.Errorf("no Platform.sh applications found")
}

// Read PSH services and routes config files
readers.ReadServices(&metaConfig, configFiles[entity.PSH_SERVICE])
readers.ReadRoutes(&metaConfig, configFiles[entity.PSH_ROUTE])

// Remove size and resources entries
readers.RemoveAllEntry(&metaConfig.Services, "size")
readers.RemoveAllEntry(&metaConfig.Applications, "size")
readers.RemoveAllEntry(&metaConfig.Services, "resources")
readers.RemoveAllEntry(&metaConfig.Applications, "resources")

// Fix storage to match Upsun format
readers.ReplaceAllEntry(&metaConfig.Applications, "local", "instance")
readers.ReplaceAllEntry(&metaConfig.Applications, "shared", "storage")
readers.RemoveAllEntry(&metaConfig.Applications, "disk")

upsunDir := filepath.Join(cwd, ".upsun")
if err := os.MkdirAll(upsunDir, os.ModePerm); err != nil {
return fmt.Errorf("could not create .upsun directory: %w", err)
}

configPath := filepath.Join(upsunDir, "config.yaml")
stat, err := os.Stat(configPath)
if err == nil && !stat.IsDir() {
cmd.Printf("The file %v already exists.\n", configPath)
if !viper.GetBool("yes") {
if viper.GetBool("no-interaction") {
return fmt.Errorf("use the -y option to overwrite the file")
}

if !terminal.AskConfirmation("Do you want to overwrite it?", true) {
return nil
}
}
}
writers.GenerateUpsunConfigFile(metaConfig, configPath)

// Move extra config
utils.TransferConfigCustom(cwd, upsunDir)

cmd.Println("Your configuration was successfully converted to the Upsun format.")
cmd.Printf("Check the generated files in %v\n", upsunDir)
return nil
}
4 changes: 4 additions & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob
cmd.PersistentFlags().BoolP("version", "V", false, fmt.Sprintf("Displays the %s version", cnf.Application.Name))
cmd.PersistentFlags().Bool("debug", false, "Enable debug logging")
cmd.PersistentFlags().Bool("no-interaction", false, "Enable non-interactive mode")
cmd.PersistentFlags().BoolP("yes", "y", false, "Assume 'yes' to all prompts")
cmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose output")
cmd.PersistentFlags().BoolP("quiet", "q", false,
"Suppress any messages and errors (stderr), while continuing to display necessary output (stdout)."+
Expand Down Expand Up @@ -153,6 +154,9 @@ func newRootCommand(cnf *config.Config, assets *vendorization.VendorAssets) *cob
validateCmd,
versionCommand,
)
if cnf.Service.ProjectConfigFlavor == "upsun" {
cmd.AddCommand(newProjectConvertCommand(cnf))
}

//nolint:errcheck
viper.BindPFlags(cmd.PersistentFlags())
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/symfony-cli/terminal v1.0.7
github.com/upsun/lib-sun v0.3.16
github.com/wk8/go-ordered-map/v2 v2.1.8
golang.org/x/crypto v0.42.0
golang.org/x/oauth2 v0.31.0
Expand All @@ -31,6 +32,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.10 // 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.4.0 // indirect
Expand Down
8 changes: 8 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.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
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.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/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.1/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.1 h1:SJCcCDjIF13KVUNdZtm3SC1uy6iWFhtvJIljqAuIbXQ=
Expand Down Expand Up @@ -127,6 +133,8 @@ 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/lib-sun v0.3.16 h1:46QTFurVKJZ/E4sGdJ+2Zi/MVAQKbTMMcdOxMaty/UQ=
github.com/upsun/lib-sun v0.3.16/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