diff --git a/.github/version/versions.txt b/.github/version/versions.txt index 324ef5fdc..1b8c2c2f8 100644 --- a/.github/version/versions.txt +++ b/.github/version/versions.txt @@ -1 +1 @@ -0.48.34 +0.48-margo.34 diff --git a/.gitmodules b/.gitmodules index 260c3bd4a..c8d25f2e1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +1,10 @@ -[submodule "landing/public"] - path = landing/public - url = https://github.com/eclipse-symphony/symphony-website.git - branch = main [submodule "docs-site/public"] path = docs-site/public url = https://github.com/eclipse-symphony/docs.git [submodule "docs-site/themes/docsy"] path = docs-site/themes/docsy - url = https://github.com/google/docsy.git \ No newline at end of file + url = https://github.com/google/docsy.git +[submodule "landing/public"] + path = landing/public + url = https://github.com/eclipse-symphony/symphony-website.git + branch = main diff --git a/cli/cmd/margo.go b/cli/cmd/margo.go new file mode 100644 index 000000000..6aa1d3ddf --- /dev/null +++ b/cli/cmd/margo.go @@ -0,0 +1,156 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package cmd + +import ( + "encoding/json" + "fmt" + + "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + "github.com/eclipse-symphony/symphony/cli/config" + "github.com/eclipse-symphony/symphony/cli/utils" + "github.com/eclipse-symphony/symphony/hydra" + "github.com/eclipse-symphony/symphony/hydra/margo/v0" + "github.com/spf13/cobra" +) + +var ( + appPackagePath string +) + +var MargoCmd = &cobra.Command{ + Use: "margo", + Short: "Margo commands", + Run: func(cmd *cobra.Command, args []string) { + fmt.Printf("\n%sPlease use either 'margo run' or 'margo list'%s\n\n", utils.ColorRed(), utils.ColorReset()) + }, +} + +var MargoRunCmd = &cobra.Command{ + Use: "run", + Short: "run a Margo application package", + Run: func(cmd *cobra.Command, args []string) { + //check context + c := config.GetMaestroConfig(configFile) + ctx := c.DefaultContext + if configContext != "" { + ctx = configContext + } + + if ctx == "" { + ctx = "default" + } + //check target + targets, err := utils.Get(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "targets", "", "", target) + if err != nil && targets != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + if len(targets) == 0 { + fmt.Printf("\n%s Target '%s' is not found%s\n\n", utils.ColorRed(), target, utils.ColorReset()) + return + } + reader := margo.MargoSolutionReader{} + solution, err := reader.Parse(hydra.AppPackageDescription{ + Path: appPackagePath, + Type: "margo", + Version: "v0", + }) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + solutionContainerName := solution.ObjectMeta.Name + + solutionContainers, err := utils.Get(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "solutioncontainers", "", "", solutionContainerName) + if err != nil && solutionContainers != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + if len(solutionContainers) != 0 { + fmt.Printf("\n%s Solution '%s' already exists%s\n\n", utils.ColorRed(), solutionContainerName, utils.ColorReset()) + return + } + + //create solution container + solutionContainer := model.SolutionContainerState{ + ObjectMeta: model.ObjectMeta{ + Name: solutionContainerName, + }, + Spec: &model.SolutionContainerSpec{}, + } + solutionContainerData, err := json.Marshal(solutionContainer) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + err = utils.Upsert(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "solution-containers", solutionContainerName, solutionContainerData) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + solutionName := solutionContainerName + "-v-v1" + solution.ObjectMeta.Name = solutionName + solution.Spec.RootResource = solutionContainerName + + //create solution + solutionData, err := json.Marshal(solution) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + err = utils.Upsert(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "solutions", solutionName, solutionData) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + + instanceName := solutionName + "-instance" + //check instance + instances, err := utils.Get(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "instances", "", "", instanceName) + if err != nil && instances != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + if len(instances) > 0 { + fmt.Printf("\n%s Solution instance '%s' already exists%s\n\n", utils.ColorRed(), instanceName, utils.ColorReset()) + return + } + //create instance + instance := Instance{ + Metadata: model.ObjectMeta{ + Name: instanceName, + }, + Spec: model.InstanceSpec{ + Solution: solutionContainerName + ":v1", + Target: model.TargetSelector{ + Name: target, + }, + }, + } + instanceData, err := json.Marshal(instance) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + err = utils.Upsert(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "instances", instanceName, instanceData) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + fmt.Printf("\n%s Solution instance created successfully%s\n\n", utils.ColorGreen(), utils.ColorReset()) + }, +} + +func init() { + MargoRunCmd.Flags().StringVarP(&appPackagePath, "package", "p", "", "Margo application package definition path") + MargoRunCmd.Flags().StringVarP(&target, "target", "t", "", "Target to run the solution on") + MargoRunCmd.MarkFlagRequired("package") + MargoRunCmd.MarkFlagRequired("target") + MargoCmd.AddCommand(MargoRunCmd) + RootCmd.AddCommand(MargoCmd) +} diff --git a/cli/cmd/run.go b/cli/cmd/run.go new file mode 100644 index 000000000..daa5cd506 --- /dev/null +++ b/cli/cmd/run.go @@ -0,0 +1,123 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package cmd + +import ( + "encoding/json" + "fmt" + + "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + "github.com/eclipse-symphony/symphony/cli/config" + "github.com/eclipse-symphony/symphony/cli/utils" + "github.com/spf13/cobra" +) + +var ( + solutionPath string + target string + instanceName string +) + +var RunCmd = &cobra.Command{ + Use: "run", + Short: "run a Margo application package", + Run: func(cmd *cobra.Command, args []string) { + //check context + c := config.GetMaestroConfig(configFile) + ctx := c.DefaultContext + if configContext != "" { + ctx = configContext + } + + if ctx == "" { + ctx = "default" + } + //check target + targets, err := utils.Get(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "targets", "", "", target) + if err != nil && targets != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + if len(targets) == 0 { + fmt.Printf("\n%s Target '%s' is not found%s\n\n", utils.ColorRed(), target, utils.ColorReset()) + return + } + //check instance + instances, err := utils.Get(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "instances", "", "", instanceName) + if err != nil && instances != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + if len(instances) > 0 { + fmt.Printf("\n%s Solution instance '%s' already exists%s\n\n", utils.ColorRed(), instanceName, utils.ColorReset()) + return + } + //check solution + solutionData, err := utils.GetArtifactFile(solutionPath) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + var solution Solution + err = json.Unmarshal(solutionData, &solution) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + solutionName := solution.Metadata.Name + solutions, err := utils.Get(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "solutions", "", "", solutionName) + if err != nil && solutions != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + if len(solutions) != 0 { + fmt.Printf("\n%s Solution '%s' already exists%s\n\n", utils.ColorRed(), solutionName, utils.ColorReset()) + return + } + //create solution + err = utils.Upsert(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "solutions", solutionName, solutionData) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + //create instance + instance := Instance{ + Metadata: model.ObjectMeta{ + Name: instanceName, + }, + Spec: model.InstanceSpec{ + Solution: solutionName, + Target: model.TargetSelector{ + Name: target, + }, + }, + } + instanceData, err := json.Marshal(instance) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + err = utils.Upsert(c.Contexts[ctx].Url, c.Contexts[ctx].User, c.Contexts[ctx].Secret, "instances", instanceName, instanceData) + if err != nil { + fmt.Printf("\n%s %s%s\n\n", utils.ColorRed(), err.Error(), utils.ColorReset()) + return + } + fmt.Printf("\n%s Solution instance created successfully%s\n\n", utils.ColorGreen(), utils.ColorReset()) + }, +} + +func init() { + RunCmd.Flags().StringVarP(&solutionPath, "solution", "s", "", "Symphony solution file path") + RunCmd.Flags().StringVarP(&target, "target", "t", "", "Target to run the solution on") + RunCmd.Flags().StringVarP(&instanceName, "name", "n", "", "Name of the solution instance") + RunCmd.Flags().StringVarP(&configFile, "config", "c", "", "Maestro CLI config file") + RunCmd.Flags().StringVarP(&configContext, "context", "", "", "Maestro CLI configuration context") + RunCmd.MarkFlagRequired("solution") + RunCmd.MarkFlagRequired("target") + RunCmd.MarkFlagRequired("name") + RootCmd.AddCommand(RunCmd) +} diff --git a/cli/cmd/samples.go b/cli/cmd/samples.go index beb3da3b8..20bccb7b5 100644 --- a/cli/cmd/samples.go +++ b/cli/cmd/samples.go @@ -150,7 +150,7 @@ var DescribeCmd = &cobra.Command{ }, } -var RunCmd = &cobra.Command{ +var SampleRunCmd = &cobra.Command{ Use: "run", Short: "run a Symphony sample", Run: func(cmd *cobra.Command, args []string) { @@ -339,14 +339,14 @@ var RemoveCmd = &cobra.Command{ } func init() { - RunCmd.Flags().StringArrayVarP(&setSwitches, "set", "s", nil, "set sample parameter as key=value") - RunCmd.Flags().StringVarP(&sampleConfigFile, "config", "c", "", "Maestro CLI config file") - RunCmd.Flags().StringVarP(&sampleConfigContext, "context", "", "", "Maestro CLI configuration context") + SampleRunCmd.Flags().StringArrayVarP(&setSwitches, "set", "s", nil, "set sample parameter as key=value") + SampleRunCmd.Flags().StringVarP(&sampleConfigFile, "config", "c", "", "Maestro CLI config file") + SampleRunCmd.Flags().StringVarP(&sampleConfigContext, "context", "", "", "Maestro CLI configuration context") RemoveCmd.Flags().StringVarP(&sampleConfigFile, "config", "c", "", "Maestro CLI config file") RemoveCmd.Flags().StringVarP(&sampleConfigContext, "context", "", "", "Maestro CLI configuration context") DescribeCmd.Flags().StringVarP(&sampleConfigFile, "config", "c", "", "Maestro CLI config file") DescribeCmd.Flags().StringVarP(&sampleConfigContext, "context", "", "", "Maestro CLI configuration context") - SamplesCmd.AddCommand(RunCmd) + SamplesCmd.AddCommand(SampleRunCmd) SamplesCmd.AddCommand(RemoveCmd) SamplesCmd.AddCommand(DescribeCmd) SamplesCmd.AddCommand(ListCmd) diff --git a/cli/go.mod b/cli/go.mod index 45c23ba44..48982f328 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -8,6 +8,8 @@ replace github.com/eclipse-symphony/symphony/api => ../api replace github.com/eclipse-symphony/symphony/coa => ../coa +replace github.com/eclipse-symphony/symphony/hydra => ../hydra + require github.com/spf13/cobra v1.8.1 require ( @@ -48,6 +50,7 @@ require ( require ( github.com/eclipse-symphony/symphony/api v0.0.0 + github.com/eclipse-symphony/symphony/hydra v0.0.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jedib0t/go-pretty/v6 v6.4.2 github.com/mattn/go-runewidth v0.0.15 // indirect diff --git a/cli/utils/api.go b/cli/utils/api.go index 26a1edad7..486e24d0a 100644 --- a/cli/utils/api.go +++ b/cli/utils/api.go @@ -13,6 +13,7 @@ import ( "fmt" "io" "net/http" + "os" "sigs.k8s.io/yaml" ) @@ -100,6 +101,25 @@ func yamlToJson(payload []byte) ([]byte, error) { return json.Marshal(o) } +func GetArtifactFile(filePath string) ([]byte, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + // Try to unmarshal as JSON first + var jsonObj interface{} + if err = json.Unmarshal(data, &jsonObj); err == nil { + return data, nil // It's already JSON, return it + } + + // If it's not JSON, try to unmarshal as YAML + if err = yaml.Unmarshal(data, &jsonObj); err == nil { + return yamlToJson(data) // Convert YAML to JSON and return it + } + + return nil, errors.New("file is neither valid JSON nor valid YAML") +} func Get(url string, username string, password string, objType string, path string, docType string, objName string) ([]interface{}, error) { token, err := Login(url, username, password) if err != nil { diff --git a/hydra/go.mod b/hydra/go.mod new file mode 100644 index 000000000..a1b206844 --- /dev/null +++ b/hydra/go.mod @@ -0,0 +1,20 @@ +module github.com/eclipse-symphony/symphony/hydra + +go 1.19 + +replace github.com/eclipse-symphony/symphony/api => ../api + +replace github.com/eclipse-symphony/symphony/coa => ../coa + +require ( + github.com/eclipse-symphony/symphony/api v0.0.0 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/eclipse-symphony/symphony/coa v0.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 // indirect + helm.sh/helm/v3 v3.10.0 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/hydra/go.sum b/hydra/go.sum new file mode 100644 index 000000000..c8152753a --- /dev/null +++ b/hydra/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9 h1:lNtcVz/3bOstm7Vebox+5m3nLh/BYWnhmc3AhXOW6oI= +golang.org/x/exp v0.0.0-20220929160808-de9c53c655b9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +helm.sh/helm/v3 v3.10.0 h1:y/MYONZ/bsld9kHwqgBX2uPggnUr5hahpjwt9/jrHlI= +helm.sh/helm/v3 v3.10.0/go.mod h1:paPw0hO5KVfrCMbi1M8+P8xdfBri3IiJiVKATZsFR94= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hydra/hydra.go b/hydra/hydra.go new file mode 100644 index 000000000..d3c022822 --- /dev/null +++ b/hydra/hydra.go @@ -0,0 +1,21 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package hydra + +import ( + "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" +) + +type AppPackageDescription struct { + Type string + Path string + Version string +} + +type SolutionReader interface { + Parse(appPackage AppPackageDescription) (model.SolutionState, error) +} diff --git a/hydra/margo/v0/margo.go b/hydra/margo/v0/margo.go new file mode 100644 index 000000000..f72cfaed1 --- /dev/null +++ b/hydra/margo/v0/margo.go @@ -0,0 +1,146 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + * SPDX-License-Identifier: MIT + */ + +package margo + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" + "github.com/eclipse-symphony/symphony/hydra" + "gopkg.in/yaml.v2" +) + +// See https://github.com/margo/margo-specifications/blob/main/system-design/app-interoperability/application-package-definition.md + +type ApplicationMetadata struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Description string `yaml:"description"` + Icon string `yaml:"icon,omitempty"` + Author string `yaml:"author,omitempty"` + AuthorEmail string `yaml:"author-email,omitempty"` + Organization string `yaml:"organization"` + OrganizationSite string `yaml:"organization-site,omitempty"` + AppTagline string `yaml:"app-tagline,omitempty"` + DescriptionLong string `yaml:"description-long,omitempty"` + LicenseFile string `yaml:"license-file,omitempty"` + AppSite string `yaml:"app-site,omitempty"` +} +type DeploymentDef struct { + RepoType string `yaml:"repo-type"` + Url string `yaml:"url"` +} +type ApplicationSpec struct { + HelmChart DeploymentDef `yaml:"helm-chart"` + DockerCompose DeploymentDef `yaml:"docker-compose"` +} +type ApplicationDescription struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata ApplicationMetadata `yaml:"metadata"` + Spec ApplicationSpec `yaml:"spec"` +} + +type MargoSolutionReader struct { +} + +// generateMetadataMap generates a map from ApplicationMetadata fields +func generateMetadataMap(metadata ApplicationMetadata) map[string]string { + metadataMap := make(map[string]string) + + if metadata.Name != "" { + metadataMap["name"] = metadata.Name + } + if metadata.Version != "" { + metadataMap["version"] = metadata.Version + } + if metadata.Description != "" { + metadataMap["description"] = metadata.Description + } + if metadata.Icon != "" { + metadataMap["icon"] = metadata.Icon + } + if metadata.Author != "" { + metadataMap["author"] = metadata.Author + } + if metadata.AuthorEmail != "" { + metadataMap["author-email"] = metadata.AuthorEmail + } + if metadata.Organization != "" { + metadataMap["organization"] = metadata.Organization + } + if metadata.OrganizationSite != "" { + metadataMap["organization-site"] = metadata.OrganizationSite + } + if metadata.AppTagline != "" { + metadataMap["app-tagline"] = metadata.AppTagline + } + if metadata.DescriptionLong != "" { + metadataMap["description-long"] = metadata.DescriptionLong + } + if metadata.LicenseFile != "" { + metadataMap["license-file"] = metadata.LicenseFile + } + if metadata.AppSite != "" { + metadataMap["app-site"] = metadata.AppSite + } + + return metadataMap +} + +func (m *MargoSolutionReader) Parse(appPackage hydra.AppPackageDescription) (model.SolutionState, error) { + if appPackage.Type != "margo" { + return model.SolutionState{}, fmt.Errorf("invalid app package type: %s", appPackage.Type) + } + if appPackage.Version != "v0" { + return model.SolutionState{}, fmt.Errorf("invalid app package version: %s", appPackage.Version) + } + + // Construct path to margo.yaml + margoPath := filepath.Join(appPackage.Path, "margo.yaml") + + // Read margo.yaml + data, err := os.ReadFile(margoPath) + if err != nil { + return model.SolutionState{}, fmt.Errorf("error reading margo.yaml: %v", err) + } + + // Deserialize margo.yaml into ApplicationDescription + var appDesc ApplicationDescription + err = yaml.Unmarshal(data, &appDesc) + if err != nil { + return model.SolutionState{}, fmt.Errorf("error unmarshalling margo.yaml: %v", err) + } + + // Construct SolutionState from ApplicationDescription + solutionState := model.SolutionState{ + ObjectMeta: model.ObjectMeta{ + Name: appDesc.Metadata.Name, + Annotations: generateMetadataMap(appDesc.Metadata), + }, + Spec: &model.SolutionSpec{ + Components: []model.ComponentSpec{}, + }, + } + + if appDesc.Spec.HelmChart.Url != "" { + solutionState.Spec.Components = append(solutionState.Spec.Components, model.ComponentSpec{ + Type: "helm.v3", + Name: appDesc.Metadata.Name, + Properties: map[string]interface{}{ + "chart": map[string]string{ + "repo": appDesc.Spec.HelmChart.Url, + "version": appDesc.Metadata.Version, + }, + }, + }) + } + + return solutionState, nil +} diff --git a/hydra/margo/v0/margo_test.go b/hydra/margo/v0/margo_test.go new file mode 100644 index 000000000..211f86ba0 --- /dev/null +++ b/hydra/margo/v0/margo_test.go @@ -0,0 +1,65 @@ +package margo + +import ( + "os" + "path/filepath" + "testing" + + "github.com/eclipse-symphony/symphony/hydra" + "gopkg.in/yaml.v2" +) + +func TestParse(t *testing.T) { + // Create a temporary directory for testing + tempDir := t.TempDir() + + // Define the content of margo.yaml + yamlContent := ` +apiVersion: v1 +kind: Application +metadata: + name: ExampleApp + version: "1.0.0" + description: This is an example application. + organization: ExampleOrg +` + + // Write the content to a file named margo.yaml in the temporary directory + margoPath := filepath.Join(tempDir, "margo.yaml") + err := os.WriteFile(margoPath, []byte(yamlContent), 0644) + if err != nil { + t.Fatalf("failed to write margo.yaml: %v", err) + } + + // Create a MockAppPackageDescription + appPackage := hydra.AppPackageDescription{ + Type: "margo", + Path: tempDir, + Version: "v0", + } + + // Create an instance of MargoSolutionReader + reader := MargoSolutionReader{} + + // Call the Parse method + _, err = reader.Parse(appPackage) + if err != nil { + t.Fatalf("Parse() returned an error: %v", err) + } + + // Add further checks to validate the parsed data if needed + // For example, you can unmarshal the data and check if the fields match the expected values + var appDesc ApplicationDescription + data, err := os.ReadFile(margoPath) + if err != nil { + t.Fatalf("failed to read margo.yaml: %v", err) + } + err = yaml.Unmarshal(data, &appDesc) + if err != nil { + t.Fatalf("failed to unmarshal margo.yaml: %v", err) + } + + if appDesc.APIVersion != "v1" || appDesc.Kind != "Application" || appDesc.Metadata.Name != "ExampleApp" { + t.Fatalf("parsed data does not match expected values: %+v", appDesc) + } +} diff --git a/landing/public b/landing/public deleted file mode 160000 index 5650a3119..000000000 --- a/landing/public +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5650a3119fb976d596472c796a53f160b569ed33