From a3845614b9a4552ef388b1c3074abc85dfe56390 Mon Sep 17 00:00:00 2001 From: Jernej Kos Date: Thu, 16 Jan 2025 14:08:14 +0100 Subject: [PATCH] feat(cmd/rofl): Separate init from create --- build/rofl/manifest.go | 28 +++++++++---- build/rofl/manifest_test.go | 4 +- cmd/rofl/build/build.go | 7 +--- cmd/rofl/common/manifest.go | 5 ++- cmd/rofl/mgmt.go | 83 ++++++++++++++++++++++--------------- 5 files changed, 79 insertions(+), 48 deletions(-) diff --git a/build/rofl/manifest.go b/build/rofl/manifest.go index 01b63de..6df5706 100644 --- a/build/rofl/manifest.go +++ b/build/rofl/manifest.go @@ -162,6 +162,16 @@ func (m *Manifest) SourceFileName() string { return m.sourceFn } +// Save serializes the manifest and writes it to the file returned by `SourceFileName`, overwriting +// any previous manifest. +func (m *Manifest) Save() error { + data, err := yaml.Marshal(m) + if err != nil { + return err + } + return os.WriteFile(m.SourceFileName(), data, 0o644) //nolint: gosec +} + // DefaultDeploymentName is the name of the default deployment that must always be defined and is // used in case no deployment is passed. const DefaultDeploymentName = "default" @@ -169,7 +179,7 @@ const DefaultDeploymentName = "default" // Deployment describes a single ROFL app deployment. type Deployment struct { // AppID is the Bech32-encoded ROFL app ID. - AppID string `yaml:"app_id" json:"app_id"` + AppID string `yaml:"app_id,omitempty" json:"app_id,omitempty"` // Network is the identifier of the network to deploy to. Network string `yaml:"network" json:"network"` // ParaTime is the identifier of the paratime to deploy to. @@ -184,12 +194,11 @@ type Deployment struct { // Validate validates the manifest for correctness. func (d *Deployment) Validate() error { - if len(d.AppID) == 0 { - return fmt.Errorf("app ID cannot be empty") - } - var appID rofl.AppID - if err := appID.UnmarshalText([]byte(d.AppID)); err != nil { - return fmt.Errorf("malformed app ID: %w", err) + if len(d.AppID) > 0 { + var appID rofl.AppID + if err := appID.UnmarshalText([]byte(d.AppID)); err != nil { + return fmt.Errorf("malformed app ID: %w", err) + } } if d.Network == "" { return fmt.Errorf("network cannot be empty") @@ -200,6 +209,11 @@ func (d *Deployment) Validate() error { return nil } +// HasAppID returns true iff the deployment has an application identifier set. +func (d *Deployment) HasAppID() bool { + return len(d.AppID) > 0 +} + // TrustRootConfig is the trust root configuration. type TrustRootConfig struct { // Height is the consensus layer block height where to take the trust root. diff --git a/build/rofl/manifest_test.go b/build/rofl/manifest_test.go index 11368ac..77ed2d0 100644 --- a/build/rofl/manifest_test.go +++ b/build/rofl/manifest_test.go @@ -56,12 +56,12 @@ func TestManifestValidation(t *testing.T) { err = m.Validate() require.ErrorContains(err, "must define at least the 'default' deployment") - // Missing app ID in deployment. + // Missing network in deployment. m.Deployments = map[string]*Deployment{ "default": {}, } err = m.Validate() - require.ErrorContains(err, "bad deployment 'default': app ID cannot be empty") + require.ErrorContains(err, "bad deployment 'default': network cannot be empty") // Invalid app ID. m.Deployments["default"].AppID = "foo" diff --git a/cmd/rofl/build/build.go b/cmd/rofl/build/build.go index a716835..5d575b6 100644 --- a/cmd/rofl/build/build.go +++ b/cmd/rofl/build/build.go @@ -8,7 +8,6 @@ import ( "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "gopkg.in/yaml.v3" coreCommon "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/cbor" @@ -45,7 +44,7 @@ var ( Run: func(_ *cobra.Command, _ []string) { cfg := cliConfig.Global() npa := common.GetNPASelection(cfg) - manifest, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName) + manifest, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName, true) fmt.Println("Building a ROFL application...") fmt.Printf("Deployment: %s\n", deploymentName) @@ -155,9 +154,7 @@ var ( deployment.Policy.Enclaves = append(deployment.Policy.Enclaves, *eid) } - // Serialize manifest and write it to file. - data, _ := yaml.Marshal(manifest) - if err = os.WriteFile(manifest.SourceFileName(), data, 0o644); err != nil { //nolint: gosec + if err = manifest.Save(); err != nil { cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err)) } } diff --git a/cmd/rofl/common/manifest.go b/cmd/rofl/common/manifest.go index d6bb912..4482c53 100644 --- a/cmd/rofl/common/manifest.go +++ b/cmd/rofl/common/manifest.go @@ -14,9 +14,12 @@ import ( // selection. // // In case there is an error in loading the manifest, it aborts the application. -func LoadManifestAndSetNPA(cfg *config.Config, npa *common.NPASelection, deployment string) (*rofl.Manifest, *rofl.Deployment) { +func LoadManifestAndSetNPA(cfg *config.Config, npa *common.NPASelection, deployment string, needAppID bool) (*rofl.Manifest, *rofl.Deployment) { manifest, d, err := MaybeLoadManifestAndSetNPA(cfg, npa, deployment) cobra.CheckErr(err) + if needAppID && !d.HasAppID() { + cobra.CheckErr(fmt.Errorf("deployment '%s' does not have an app ID set, maybe you need to run `oasis rofl create`", deployment)) + } return manifest, d } diff --git a/cmd/rofl/mgmt.go b/cmd/rofl/mgmt.go index 1363b4c..76ed367 100644 --- a/cmd/rofl/mgmt.go +++ b/cmd/rofl/mgmt.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "github.com/spf13/cobra" flag "github.com/spf13/pflag" @@ -36,11 +37,12 @@ var ( appTEE string appKind string deploymentName string + doUpdate bool initCmd = &cobra.Command{ - Use: "init [--tee TEE] [--kind KIND]", - Short: "Create a new ROFL app and initialize the manifest", - Args: cobra.ExactArgs(1), + Use: "init [] [--tee TEE] [--kind KIND]", + Short: "Initialize a ROFL app manifest", + Args: cobra.MaximumNArgs(1), Run: func(_ *cobra.Command, args []string) { cfg := cliConfig.Global() npa := common.GetNPASelection(cfg) @@ -57,7 +59,15 @@ var ( } // TODO: Support an interactive mode. - appName := args[0] + var appName string + if len(args) > 0 { + appName = args[0] + } else { + // Infer from current directory. + wd, err := os.Getwd() + cobra.CheckErr(err) + appName = filepath.Base(wd) + } // Fail in case there is an existing manifest. if buildRofl.ManifestExists() { cobra.CheckErr("refusing to overwrite existing manifest") @@ -73,7 +83,6 @@ var ( // Generate manifest and a default policy which does not accept any enclaves. deployment := &buildRofl.Deployment{ - AppID: rofl.NewAppIDGlobalName("").String(), // Temporary for initial validation. Network: npa.NetworkName, ParaTime: npa.ParaTimeName, Admin: npa.AccountName, @@ -104,7 +113,7 @@ var ( Memory: 512, CPUCount: 1, Storage: &buildRofl.StorageConfig{ - Kind: buildRofl.StorageKindDiskEphemeral, + Kind: buildRofl.StorageKindDiskPersistent, Size: 512, }, }, @@ -125,32 +134,15 @@ var ( fmt.Printf(" ParaTime: %s\n", deployment.ParaTime) fmt.Printf(" Admin: %s\n", deployment.Admin) - idScheme, ok := identifierSchemes[scheme] - if !ok { - cobra.CheckErr(fmt.Errorf("unknown scheme %s", scheme)) - } - - // Register a new ROFL application to determine the identifier. - tx := rofl.NewCreateTx(nil, &rofl.Create{ - Policy: *deployment.Policy, - Scheme: idScheme, - }) - - acc := common.LoadAccount(cfg, npa.AccountName) - sigTx, meta, err := common.SignParaTimeTransaction(ctx, npa, acc, conn, tx, nil) - cobra.CheckErr(err) - - var appID rofl.AppID - common.BroadcastTransaction(ctx, npa.ParaTime, conn, sigTx, meta, &appID) - deployment.AppID = appID.String() - - fmt.Printf("Created ROFL application: %s\n", appID) - // Serialize manifest and write it to file. + const manifestFn = "rofl.yml" data, _ := yaml.Marshal(manifest) - if err = os.WriteFile("rofl.yml", data, 0o644); err != nil { //nolint: gosec + if err = os.WriteFile(manifestFn, data, 0o644); err != nil { //nolint: gosec cobra.CheckErr(fmt.Errorf("failed to write manifest: %w", err)) } + + fmt.Printf("Created manifest in '%s'.\n", manifestFn) + fmt.Printf("Run `oasis rofl create --update-manifest` to register your ROFL app and configure an app ID.\n") }, } @@ -163,11 +155,15 @@ var ( npa := common.GetNPASelection(cfg) txCfg := common.GetTransactionConfig() - var policy *rofl.AppAuthPolicy + var ( + policy *rofl.AppAuthPolicy + manifest *buildRofl.Manifest + deployment *buildRofl.Deployment + ) if len(args) > 0 { policy = loadPolicy(args[0]) } else { - _, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName) + manifest, deployment = roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName, false) policy = deployment.Policy } @@ -208,6 +204,26 @@ var ( } fmt.Printf("Created ROFL application: %s\n", appID) + + if deployment != nil { + switch doUpdate { + case false: + // Ask the user to update the manifest manually. + fmt.Println("Update the manifest with the following app identifier to use the new app:") + fmt.Println() + fmt.Printf("deployments:\n") + fmt.Printf(" %s:\n", deploymentName) + fmt.Printf(" app_id: %s\n", appID) + fmt.Println() + case true: + // Update the manifest with the given enclave identities, overwriting existing ones. + deployment.AppID = appID.String() + + if err = manifest.Save(); err != nil { + cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err)) + } + } + } }, } @@ -228,7 +244,7 @@ var ( rawAppID = args[0] policy = loadPolicy(policyFn) } else { - _, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName) + _, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName, true) rawAppID = deployment.AppID if adminAddress == "" && deployment.Admin != "" { @@ -305,7 +321,7 @@ var ( if len(args) > 0 { rawAppID = args[0] } else { - _, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName) + _, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName, true) rawAppID = deployment.AppID } var appID rofl.AppID @@ -354,7 +370,7 @@ var ( if len(args) > 0 { rawAppID = args[0] } else { - _, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName) + _, deployment := roflCommon.LoadManifestAndSetNPA(cfg, npa, deploymentName, true) rawAppID = deployment.AppID } var appID rofl.AppID @@ -435,6 +451,7 @@ func init() { createCmd.Flags().AddFlagSet(common.RuntimeTxFlags) createCmd.Flags().AddFlagSet(deploymentFlags) createCmd.Flags().StringVar(&scheme, "scheme", "cn", "app ID generation scheme: creator+round+index [cri] or creator+nonce [cn]") + createCmd.Flags().BoolVar(&doUpdate, "update-manifest", false, "automatically update the manifest") updateCmd.Flags().AddFlagSet(common.SelectorFlags) updateCmd.Flags().AddFlagSet(common.RuntimeTxFlags)