diff --git a/cmd/rofl/machine/mgmt.go b/cmd/rofl/machine/mgmt.go index fe9a9730..3dfd57a4 100644 --- a/cmd/rofl/machine/mgmt.go +++ b/cmd/rofl/machine/mgmt.go @@ -113,9 +113,10 @@ var ( } changeAdminCmd = &cobra.Command{ - Use: "change-admin [ | :] ", - Short: "Change the machine administrator", - Args: cobra.RangeArgs(1, 2), + Use: "set-admin [ | :] ", + Short: "Change the machine administrator", + Aliases: []string{"change-admin"}, + Args: cobra.RangeArgs(1, 2), Run: func(_ *cobra.Command, args []string) { txCfg := common.GetTransactionConfig() mCfg, err := resolveMachineCfg(args, &roflCommon.ManifestOptions{ diff --git a/cmd/rofl/rofl.go b/cmd/rofl/rofl.go index 10242e68..ccc7d0c3 100644 --- a/cmd/rofl/rofl.go +++ b/cmd/rofl/rofl.go @@ -29,6 +29,7 @@ func init() { Cmd.AddCommand(identityCmd) Cmd.AddCommand(secretCmd) Cmd.AddCommand(upgradeCmd) + Cmd.AddCommand(setAdminCmd) Cmd.AddCommand(provider.Cmd) Cmd.AddCommand(machine.Cmd) } diff --git a/cmd/rofl/set_admin.go b/cmd/rofl/set_admin.go new file mode 100644 index 00000000..7eb9876a --- /dev/null +++ b/cmd/rofl/set_admin.go @@ -0,0 +1,107 @@ +package rofl + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/connection" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/rofl" + + buildRofl "github.com/oasisprotocol/cli/build/rofl" + "github.com/oasisprotocol/cli/cmd/common" + roflCommon "github.com/oasisprotocol/cli/cmd/rofl/common" + cliConfig "github.com/oasisprotocol/cli/config" +) + +var setAdminCmd = &cobra.Command{ + Use: "set-admin ", + Short: "Change the administrator of the application in ROFL", + Aliases: []string{"change-admin"}, + Args: cobra.ExactArgs(1), + Run: func(_ *cobra.Command, args []string) { + txCfg := common.GetTransactionConfig() + + manifest, deployment, npa := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{ + NeedAppID: true, + NeedAdmin: true, + }) + + var appID rofl.AppID + if err := appID.UnmarshalText([]byte(deployment.AppID)); err != nil { + cobra.CheckErr(fmt.Errorf("malformed ROFL app ID: %w", err)) + } + + npa.MustHaveAccount() + npa.MustHaveParaTime() + + if deployment.Policy == nil { + cobra.CheckErr("no policy configured in the manifest") + } + + oldAdminAddr, _, err := common.ResolveLocalAccountOrAddress(npa.Network, deployment.Admin) + if err != nil { + cobra.CheckErr(fmt.Errorf("bad current administrator address: %w", err)) + } + + newAdminAddr, newAdminEthAddr, err := common.ResolveLocalAccountOrAddress(npa.Network, args[0]) + if err != nil { + cobra.CheckErr(fmt.Errorf("invalid new admin address: %w", err)) + } + + if *oldAdminAddr == *newAdminAddr { + fmt.Println("New admin is the same as the current admin, nothing to do.") + return + } + + // When not in offline mode, connect to the given network endpoint. + ctx := context.Background() + var conn connection.Connection + if !txCfg.Offline { + conn, err = connection.Connect(ctx, npa.Network) + cobra.CheckErr(err) + } + + newAdminStr := newAdminAddr.String() + if newAdminEthAddr != nil { + newAdminStr = newAdminEthAddr.Hex() + } + + fmt.Printf("App ID: %s\n", deployment.AppID) + fmt.Printf("Old admin: %s\n", common.PrettyAddress(oldAdminAddr.String())) + fmt.Printf("New admin: %s\n", common.PrettyAddress(newAdminStr)) + + secrets := buildRofl.PrepareSecrets(deployment.Secrets) + + tx := rofl.NewUpdateTx(nil, &rofl.Update{ + ID: appID, + Policy: *deployment.Policy.AsDescriptor(), + Admin: newAdminAddr, + Metadata: manifest.GetMetadata(roflCommon.DeploymentName), + Secrets: secrets, + }) + + acc := common.LoadAccount(cliConfig.Global(), npa.AccountName) + sigTx, meta, err := common.SignParaTimeTransaction(ctx, npa, acc, conn, tx, nil) + cobra.CheckErr(err) + + if !common.BroadcastOrExportTransaction(ctx, npa, conn, sigTx, meta, nil) { + return + } + + // Transaction succeeded — update the manifest with the new admin. + deployment.Admin = args[0] + if err = manifest.Save(); err != nil { + cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err)) + } + + fmt.Printf("ROFL admin changed to %s.\n", common.PrettyAddress(newAdminStr)) + }, +} + +func init() { + common.AddAccountFlag(setAdminCmd) + setAdminCmd.Flags().AddFlagSet(common.RuntimeTxFlags) + setAdminCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags) +} diff --git a/docs/rofl.md b/docs/rofl.md index 76a9d152..aae89e84 100644 --- a/docs/rofl.md +++ b/docs/rofl.md @@ -325,6 +325,33 @@ their latest versions. This includes: ![code shell](../examples/rofl/upgrade.in.static) +### Change ROFL app administrator {#set-admin} + +Run `rofl set-admin` to transfer ownership of a ROFL app to a new +administrator. The transaction is signed by the current admin and, on success, +the manifest is updated with the new admin. + +![code shell](../examples/rofl/set-admin.in.static) + +### Change ROFL machine administrator {#machine-set-admin} + +Run `rofl machine set-admin` to change the administrator of an individual +machine instance. + +![code shell](../examples/rofl/machine-set-admin.in.static) + +:::info ROFL admin vs machine admin + +The **ROFL admin** (changed via `oasis rofl set-admin`) owns the +application — transfer ownership, upgrades, policy changes, removal. + +The **machine admin** (changed via `oasis rofl machine set-admin`) manages +an individual machine instance — execution, restarts, stops. + +These are independent roles. + +::: + ### Remove ROFL app from the network {#remove} Run `rofl remove` to deregister your ROFL app: diff --git a/examples/rofl/machine-set-admin.in.static b/examples/rofl/machine-set-admin.in.static new file mode 100644 index 00000000..f6d9993f --- /dev/null +++ b/examples/rofl/machine-set-admin.in.static @@ -0,0 +1 @@ +oasis rofl machine set-admin [ | :] diff --git a/examples/rofl/set-admin.in.static b/examples/rofl/set-admin.in.static new file mode 100644 index 00000000..e7506af8 --- /dev/null +++ b/examples/rofl/set-admin.in.static @@ -0,0 +1 @@ +oasis rofl set-admin