Skip to content

Commit f729982

Browse files
committed
Add oasis rofl public-var subcommand
1 parent 7e8b7e0 commit f729982

3 files changed

Lines changed: 415 additions & 0 deletions

File tree

cmd/rofl/public_var.go

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
package rofl
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"sort"
8+
"unicode/utf8"
9+
10+
"github.com/spf13/cobra"
11+
12+
buildDotenv "github.com/oasisprotocol/cli/build/dotenv"
13+
"github.com/oasisprotocol/cli/cmd/common"
14+
roflCommon "github.com/oasisprotocol/cli/cmd/rofl/common"
15+
)
16+
17+
const publicVarMetadataPrefix = "env."
18+
19+
var (
20+
publicVarCmd = &cobra.Command{
21+
Use: "public-var",
22+
Short: "Public variable management commands",
23+
Long: "Public variable management commands.\n\n" +
24+
"Values are stored unencrypted in ROFL app metadata and exposed to containers as\n" +
25+
"environment variables. Use `oasis rofl secret` for confidential values.",
26+
}
27+
28+
publicVarSetCmd = &cobra.Command{
29+
Use: "set <name> <file>|-",
30+
Short: "Set a public variable in the manifest, reading the value from file or stdin",
31+
Args: cobra.ExactArgs(2),
32+
Run: func(_ *cobra.Command, args []string) {
33+
publicVarName := args[0]
34+
publicVarFn := args[1]
35+
36+
manifest, deployment, _ := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{
37+
NeedAppID: false,
38+
NeedAdmin: false,
39+
})
40+
41+
key, err := publicVarMetadataKey(publicVarName)
42+
if err != nil {
43+
cobra.CheckErr(err)
44+
}
45+
46+
// Read public variable.
47+
var publicVarValueRaw []byte
48+
if publicVarFn == "-" {
49+
publicVarValueRaw, err = io.ReadAll(os.Stdin)
50+
if err != nil {
51+
cobra.CheckErr(fmt.Errorf("failed to read public variable from standard input: %w", err))
52+
}
53+
} else {
54+
publicVarValueRaw, err = os.ReadFile(publicVarFn)
55+
if err != nil {
56+
cobra.CheckErr(fmt.Errorf("failed to read public variable from file: %w", err))
57+
}
58+
}
59+
publicVarValue, err := parsePublicVarValue(publicVarValueRaw)
60+
if err != nil {
61+
cobra.CheckErr(err)
62+
}
63+
64+
if _, ok := deployment.Metadata[key]; ok {
65+
common.CheckForceErr(fmt.Errorf("the public variable named '%s' for deployment '%s' already exists", publicVarName, roflCommon.DeploymentName))
66+
}
67+
if deployment.Metadata == nil {
68+
deployment.Metadata = make(map[string]string)
69+
}
70+
deployment.Metadata[key] = publicVarValue
71+
72+
// Update manifest.
73+
if err = manifest.Save(); err != nil {
74+
cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err))
75+
}
76+
77+
fmt.Printf("Run `oasis rofl update` to update your ROFL app's on-chain configuration.\n")
78+
},
79+
}
80+
81+
// publicVarImportCmd bulk-imports public variables from a .env file (key=value with # comments).
82+
// Supports '-' to read from stdin. Existing public variables are replaced only with --force.
83+
publicVarImportCmd = &cobra.Command{
84+
Use: "import <dot-env-file>|-",
85+
Short: "Import multiple public variables from a .env file",
86+
Args: cobra.ExactArgs(1),
87+
Run: func(_ *cobra.Command, args []string) {
88+
envFn := args[0]
89+
90+
manifest, deployment, _ := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{
91+
NeedAppID: false,
92+
NeedAdmin: false,
93+
})
94+
95+
// Read .env data (supports stdin via "-").
96+
var (
97+
raw []byte
98+
err error
99+
)
100+
if envFn == "-" {
101+
raw, err = io.ReadAll(os.Stdin)
102+
if err != nil {
103+
cobra.CheckErr(fmt.Errorf("failed to read .env from standard input: %w", err))
104+
}
105+
} else {
106+
raw, err = os.ReadFile(envFn)
107+
if err != nil {
108+
cobra.CheckErr(fmt.Errorf("failed to read .env file: %w", err))
109+
}
110+
}
111+
112+
entries, err := buildDotenv.Parse(string(raw))
113+
if err != nil {
114+
cobra.CheckErr(fmt.Errorf("failed to parse .env: %w", err))
115+
}
116+
if len(entries) == 0 {
117+
fmt.Println("No key=value pairs found in the provided .env input.")
118+
return
119+
}
120+
121+
// Deterministic iteration for stable updates.
122+
names := make([]string, 0, len(entries))
123+
for k := range entries {
124+
names = append(names, k)
125+
}
126+
sort.Strings(names)
127+
128+
if deployment.Metadata == nil {
129+
deployment.Metadata = make(map[string]string)
130+
}
131+
132+
var imported, updated int
133+
for _, name := range names {
134+
key, err := publicVarMetadataKey(name)
135+
if err != nil {
136+
cobra.CheckErr(err)
137+
}
138+
139+
if _, ok := deployment.Metadata[key]; ok {
140+
common.CheckForceErr(fmt.Errorf("the public variable named '%s' for deployment '%s' already exists", name, roflCommon.DeploymentName))
141+
updated++
142+
} else {
143+
imported++
144+
}
145+
deployment.Metadata[key] = entries[name]
146+
}
147+
148+
// Update manifest.
149+
if err = manifest.Save(); err != nil {
150+
cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err))
151+
}
152+
153+
src := envFn
154+
if envFn == "-" {
155+
src = "stdin"
156+
}
157+
fmt.Printf("Imported %d public variables, updated %d existing from '%s'.\n", imported, updated, src)
158+
fmt.Printf("Run `oasis rofl update` to update your ROFL app's on-chain configuration.\n")
159+
},
160+
}
161+
162+
publicVarGetCmd = &cobra.Command{
163+
Use: "get <name>",
164+
Short: "Show the given public variable",
165+
Args: cobra.ExactArgs(1),
166+
Run: func(_ *cobra.Command, args []string) {
167+
publicVarName := args[0]
168+
169+
_, deployment, _ := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{
170+
NeedAppID: false,
171+
NeedAdmin: false,
172+
})
173+
174+
key, err := publicVarMetadataKey(publicVarName)
175+
if err != nil {
176+
cobra.CheckErr(err)
177+
}
178+
value, ok := deployment.Metadata[key]
179+
if !ok {
180+
cobra.CheckErr(fmt.Errorf("public variable named '%s' does not exist for deployment '%s'", publicVarName, roflCommon.DeploymentName))
181+
return // Lint doesn't know that cobra.CheckErr never returns.
182+
}
183+
184+
fmt.Printf("Name: %s\n", publicVarName)
185+
fmt.Printf("Value: %s\n", value)
186+
fmt.Printf("Size: %d bytes\n", len(value))
187+
},
188+
}
189+
190+
publicVarRmCmd = &cobra.Command{
191+
Use: "rm <name>",
192+
Short: "Remove the given public variable from the manifest",
193+
Args: cobra.ExactArgs(1),
194+
Run: func(_ *cobra.Command, args []string) {
195+
publicVarName := args[0]
196+
197+
manifest, deployment, _ := roflCommon.LoadManifestAndSetNPA(&roflCommon.ManifestOptions{
198+
NeedAppID: false,
199+
NeedAdmin: false,
200+
})
201+
202+
key, err := publicVarMetadataKey(publicVarName)
203+
if err != nil {
204+
cobra.CheckErr(err)
205+
}
206+
if _, ok := deployment.Metadata[key]; !ok {
207+
cobra.CheckErr(fmt.Errorf("public variable named '%s' does not exist for deployment '%s'", publicVarName, roflCommon.DeploymentName))
208+
}
209+
delete(deployment.Metadata, key)
210+
if len(deployment.Metadata) == 0 {
211+
deployment.Metadata = nil
212+
}
213+
214+
// Update manifest.
215+
if err := manifest.Save(); err != nil {
216+
cobra.CheckErr(fmt.Errorf("failed to update manifest: %w", err))
217+
}
218+
219+
fmt.Printf("Run `oasis rofl update` to update your ROFL app's on-chain configuration.\n")
220+
},
221+
}
222+
)
223+
224+
func publicVarMetadataKey(name string) (string, error) {
225+
if err := validatePublicVarName(name); err != nil {
226+
return "", err
227+
}
228+
return publicVarMetadataPrefix + name, nil
229+
}
230+
231+
func validatePublicVarName(name string) error {
232+
if name == "" {
233+
return fmt.Errorf("public variable name cannot be empty")
234+
}
235+
for _, ch := range name {
236+
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_' {
237+
continue
238+
}
239+
return fmt.Errorf("public variable name '%s' is invalid, only ASCII letters, digits and '_' are allowed", name)
240+
}
241+
return nil
242+
}
243+
244+
func parsePublicVarValue(raw []byte) (string, error) {
245+
if !utf8.Valid(raw) {
246+
return "", fmt.Errorf("public variable values must be valid UTF-8")
247+
}
248+
return string(raw), nil
249+
}
250+
251+
func init() {
252+
publicVarSetCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags)
253+
publicVarSetCmd.Flags().AddFlagSet(common.ForceFlag)
254+
publicVarCmd.AddCommand(publicVarSetCmd)
255+
256+
// public-var import flags and registration.
257+
publicVarImportCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags)
258+
publicVarImportCmd.Flags().AddFlagSet(common.ForceFlag)
259+
publicVarCmd.AddCommand(publicVarImportCmd)
260+
261+
publicVarGetCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags)
262+
publicVarCmd.AddCommand(publicVarGetCmd)
263+
264+
publicVarRmCmd.Flags().AddFlagSet(roflCommon.DeploymentFlags)
265+
publicVarCmd.AddCommand(publicVarRmCmd)
266+
}

0 commit comments

Comments
 (0)