Skip to content

Commit 035b662

Browse files
authored
feat:support env and appenv instruction (#2179)
1 parent f63a14c commit 035b662

File tree

8 files changed

+179
-4
lines changed

8 files changed

+179
-4
lines changed

build/kubefile/command/command.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ const (
3434
Label = "label"
3535
Maintainer = "maintainer"
3636

37+
Env = "env"
38+
AppEnv = "appenv"
39+
3740
// Deprecated
3841
Cmd = "cmd"
3942

@@ -73,4 +76,6 @@ var SupportedCommands = map[string]struct{}{
7376
CNI: {},
7477
CSI: {},
7578
KUBEVERSION: {},
79+
Env: {},
80+
AppEnv: {},
7681
}

build/kubefile/parser/kubefile.go

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ import (
2626
parse2 "github.com/containers/buildah/pkg/parse"
2727
"github.com/moby/buildkit/frontend/dockerfile/shell"
2828
"github.com/pkg/errors"
29-
"github.com/sirupsen/logrus"
30-
3129
"github.com/sealerio/sealer/build/kubefile/command"
30+
v1 "github.com/sealerio/sealer/pkg/define/application/v1"
3231
"github.com/sealerio/sealer/pkg/define/application/version"
3332
v12 "github.com/sealerio/sealer/pkg/define/image/v1"
3433
"github.com/sealerio/sealer/pkg/define/options"
3534
"github.com/sealerio/sealer/pkg/imageengine"
35+
"github.com/sirupsen/logrus"
3636
)
3737

3838
// LegacyContext stores legacy information during the process of parsing.
@@ -58,6 +58,17 @@ type KubefileResult struct {
5858
// LAUNCH ["myapp1","myapp2"]
5959
LaunchedAppNames []string
6060

61+
// GlobalEnv is a set of key value pair.
62+
// set to sealer image some default parameters which is in global level.
63+
// user could overwrite it through v2.ClusterSpec at run stage.
64+
GlobalEnv map[string]string
65+
66+
// AppEnv is a set of key value pair.
67+
// it is app level, only this app will be aware of its existence,
68+
// it is used to render app files, or as an environment variable for app startup and deletion commands
69+
// it takes precedence over GlobalEnv.
70+
AppEnvMap map[string]map[string]string
71+
6172
// Applications structured APP instruction and register it to this map
6273
// APP myapp local://app.yaml
6374
Applications map[string]version.VersionedApplication
@@ -93,6 +104,8 @@ func (kp *KubefileParser) generateResult(mainNode *Node) (*KubefileResult, error
93104
result = &KubefileResult{
94105
Applications: map[string]version.VersionedApplication{},
95106
ApplicationConfigs: map[string]*v12.ApplicationConfig{},
107+
GlobalEnv: map[string]string{},
108+
AppEnvMap: map[string]map[string]string{},
96109
legacyContext: LegacyContext{
97110
files: []string{},
98111
directories: []string{},
@@ -179,6 +192,18 @@ func (kp *KubefileParser) generateResult(mainNode *Node) (*KubefileResult, error
179192
}
180193
}
181194

195+
// register app with all env list.
196+
for appName, appEnv := range result.AppEnvMap {
197+
app := result.Applications[appName]
198+
result.Applications[appName] = &v1.Application{
199+
NameVar: app.Name(),
200+
TypeVar: app.Type(),
201+
FilesVar: app.Files(),
202+
VersionVar: app.Version(),
203+
AppEnv: appEnv,
204+
}
205+
}
206+
182207
return result, nil
183208
}
184209

@@ -188,6 +213,12 @@ func (kp *KubefileParser) processOnCmd(result *KubefileResult, node *Node) error
188213
case command.Label, command.Maintainer, command.Add, command.Arg, command.From, command.Run:
189214
result.Dockerfile = mergeLines(result.Dockerfile, node.Original)
190215
return nil
216+
case command.Env:
217+
// update global env to dockerfile at the same, for using it at build stage.
218+
result.Dockerfile = mergeLines(result.Dockerfile, node.Original)
219+
return kp.processGlobalEnv(node, result)
220+
case command.AppEnv:
221+
return kp.processAppEnv(node, result)
191222
case command.App:
192223
_, err := kp.processApp(node, result)
193224
return err
@@ -304,6 +335,75 @@ func (kp *KubefileParser) processAppCmds(node *Node, result *KubefileResult) err
304335
return nil
305336
}
306337

338+
func (kp *KubefileParser) processAppEnv(node *Node, result *KubefileResult) error {
339+
var (
340+
appName = ""
341+
envList []string
342+
)
343+
344+
// first node value is the command
345+
for ptr := node.Next; ptr != nil; ptr = ptr.Next {
346+
val := ptr.Value
347+
// record the first word to be the app name
348+
if appName == "" {
349+
appName = val
350+
continue
351+
}
352+
envList = append(envList, val)
353+
}
354+
355+
if appName == "" {
356+
return errors.New("app name should be specified in the APPENV instruction")
357+
}
358+
359+
if _, ok := result.Applications[appName]; !ok {
360+
return fmt.Errorf("the specified app name(%s) for `APPENV` should be exist", appName)
361+
}
362+
363+
tmpEnv := make(map[string]string)
364+
for _, elem := range envList {
365+
var kv []string
366+
if kv = strings.SplitN(elem, "=", 2); len(kv) != 2 {
367+
continue
368+
}
369+
tmpEnv[kv[0]] = kv[1]
370+
}
371+
372+
appEnv := result.AppEnvMap[appName]
373+
if appEnv == nil {
374+
appEnv = make(map[string]string)
375+
}
376+
377+
for k, v := range tmpEnv {
378+
appEnv[k] = v
379+
}
380+
381+
result.AppEnvMap[appName] = appEnv
382+
return nil
383+
}
384+
385+
func (kp *KubefileParser) processGlobalEnv(node *Node, result *KubefileResult) error {
386+
valueList := strings.SplitN(node.Original, "ENV ", 2)
387+
if len(valueList) != 2 {
388+
return fmt.Errorf("line %d: invalid ENV instruction: %s", node.StartLine, node.Original)
389+
}
390+
envs := valueList[1]
391+
392+
for _, elem := range strings.Split(envs, " ") {
393+
if elem == "" {
394+
continue
395+
}
396+
397+
var kv []string
398+
if kv = strings.SplitN(elem, "=", 2); len(kv) != 2 {
399+
continue
400+
}
401+
result.GlobalEnv[kv[0]] = kv[1]
402+
}
403+
404+
return nil
405+
}
406+
307407
func (kp *KubefileParser) processCmd(node *Node, result *KubefileResult) error {
308408
original := node.Original
309409
cmd := strings.Split(original, "CMD ")

build/kubefile/parser/parse.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ func init() {
214214
command.Run: parseMaybeJSON,
215215
command.App: parseMaybeJSONToList,
216216
command.AppCmds: parseMaybeJSONToList,
217+
command.Env: parseNameOrNameVal,
218+
command.AppEnv: parseMaybeJSONToList,
217219
command.KUBEVERSION: parseString,
218220
command.CNI: parseMaybeJSONToList,
219221
command.CSI: parseMaybeJSONToList,

build/kubefile/parser/parse_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,54 @@ FROM scratch
214214
assert.Equal(t, expectedResult.RawCmds, result.RawCmds)
215215
}
216216

217+
func TestParserEnv(t *testing.T) {
218+
appFilePath := "./test/kube-nginx-deployment/deployment.yaml"
219+
imageEngine := testImageEngine{}
220+
opts := options.BuildOptions{
221+
PullPolicy: "missing",
222+
}
223+
224+
buildCxt, err := setupTempContext()
225+
assert.Equal(t, nil, err)
226+
defer func() {
227+
_ = os.RemoveAll(buildCxt)
228+
}()
229+
230+
opts.ContextDir = buildCxt
231+
testParser = NewParser(testAppRootPath, opts, imageEngine, platformParse.DefaultPlatform())
232+
233+
var (
234+
text = fmt.Sprintf(`
235+
FROM scratch
236+
APP app1 local://%s
237+
ENV globalKey=globalValue
238+
APPENV app1 key1=value1 key2=value2
239+
APPENV app1 key1=value3 key2=value3
240+
LAUNCH ["app1"]`, appFilePath)
241+
)
242+
243+
reader := bytes.NewReader([]byte(text))
244+
result, err := testParser.ParseKubefile(reader)
245+
if err != nil {
246+
t.Fatalf("failed to parse kubefile: %s", err)
247+
}
248+
defer func() {
249+
_ = result.CleanLegacyContext()
250+
}()
251+
252+
expectedResult := &KubefileResult{
253+
GlobalEnv: map[string]string{
254+
"globalKey": "globalValue",
255+
},
256+
AppEnvMap: map[string]map[string]string{
257+
"app1": {"key1": "value3", "key2": "value3"},
258+
},
259+
}
260+
261+
assert.Equal(t, expectedResult.GlobalEnv, result.GlobalEnv)
262+
assert.Equal(t, expectedResult.AppEnvMap, result.AppEnvMap)
263+
}
264+
217265
func setupTempContext() (string, error) {
218266
tmpDir, err := os.MkdirTemp("/tmp/", "sealer-test")
219267
if err != nil {

cmd/sealer/cmd/image/build.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ func buildImageExtensionOnResult(result *parser.KubefileResult, imageType string
430430
Applications: []version2.VersionedApplication{},
431431
Launch: v12.Launch{},
432432
SchemaVersion: v12.ImageSpecSchemaVersionV1Beta1,
433+
Env: result.GlobalEnv,
433434
BuildClient: v12.BuildClient{
434435
SealerVersion: version.Get().GitVersion,
435436
BuildahVersion: define.Version,

pkg/define/application/v1/application.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ type Application struct {
2828
TypeVar string `json:"type,omitempty"`
2929
FilesVar []string `json:"files,omitempty"`
3030
VersionVar string `json:"version,omitempty"`
31+
32+
// AppEnv is a set of key value pair.
33+
// it is app level, only this app will be aware of its existence,
34+
// it is used to render app files, or as an environment variable for app startup and deletion commands
35+
AppEnv map[string]string `json:"env,omitempty"`
3136
}
3237

3338
func (app *Application) Version() string {
@@ -42,6 +47,10 @@ func (app *Application) Type() string {
4247
return app.TypeVar
4348
}
4449

50+
func (app *Application) Files() []string {
51+
return app.FilesVar
52+
}
53+
4554
func (app *Application) LaunchCmd(appRoot string, launchCmds []string) string {
4655
if len(launchCmds) != 0 {
4756
var cmds []string

pkg/define/application/version/version.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ type VersionedApplication interface {
2222

2323
Type() string
2424

25+
Files() []string
26+
2527
LaunchCmd(appRoot string, launchCmds []string) string
2628
}

pkg/define/image/v1/sealer_image.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ package v1
1717
import (
1818
"encoding/json"
1919

20-
application_v1 "github.com/sealerio/sealer/pkg/define/application/v1"
20+
applicationV1 "github.com/sealerio/sealer/pkg/define/application/v1"
2121
"github.com/sealerio/sealer/pkg/define/application/version"
2222
apiv1 "github.com/sealerio/sealer/types/api/v1"
2323

@@ -99,6 +99,11 @@ type ImageExtension struct {
9999
// Labels are metadata to the sealer image
100100
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
101101

102+
// Env is a set of key value pair.
103+
// set to sealer image some default parameters which is in global level.
104+
// user could overwrite it through v2.ClusterSpec at run stage.
105+
Env map[string]string `json:"env,omitempty"`
106+
102107
// launch spec will declare
103108
Launch Launch `json:"launch,omitempty"`
104109
}
@@ -139,9 +144,11 @@ type v1ImageExtension struct {
139144
// Labels are metadata to the sealer image
140145
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
141146
// applications in the sealer image
142-
Applications []application_v1.Application `json:"applications,omitempty"`
147+
Applications []applicationV1.Application `json:"applications,omitempty"`
143148
// launch spec will declare
144149
Launch Launch `json:"launch,omitempty"`
150+
// Env global env
151+
Env map[string]string `json:"env,omitempty"`
145152
}
146153

147154
func (ie *ImageExtension) UnmarshalJSON(data []byte) error {
@@ -154,6 +161,7 @@ func (ie *ImageExtension) UnmarshalJSON(data []byte) error {
154161
(*ie).BuildClient = v1Ex.BuildClient
155162
(*ie).SchemaVersion = v1Ex.SchemaVersion
156163
(*ie).Labels = v1Ex.Labels
164+
(*ie).Env = v1Ex.Env
157165
(*ie).Type = v1Ex.Type
158166
(*ie).Applications = make([]version.VersionedApplication, len(v1Ex.Applications))
159167
for i, app := range v1Ex.Applications {

0 commit comments

Comments
 (0)