@@ -17,12 +17,38 @@ import (
1717 buildRofl "github.com/oasisprotocol/cli/build/rofl"
1818)
1919
20- // validateApp validates the ROFL app manifest.
21- func validateApp (manifest * buildRofl.Manifest ) error {
20+ // ValidationOpts represents options for the validation process.
21+ type ValidationOpts struct {
22+ // Offline indicates whether the validation should be performed without network access.
23+ Offline bool
24+ }
25+
26+ // AppExtraConfig represents extra configuration for the ROFL app.
27+ type AppExtraConfig struct {
28+ // Ports are the port mappings exposed by the app.
29+ Ports []* PortMapping
30+ }
31+
32+ // PortMapping represents a port mapping.
33+ type PortMapping struct {
34+ // ServiceName is the name of the service.
35+ ServiceName string
36+ // Port is the port number.
37+ Port string
38+ // ProxyMode is the proxy mode for the port.
39+ ProxyMode string
40+ // GenericDomain is the generic domain name.
41+ GenericDomain string
42+ // CustomDomain is the custom domain name (if any).
43+ CustomDomain string
44+ }
45+
46+ // ValidateApp validates the ROFL app manifest.
47+ func ValidateApp (manifest * buildRofl.Manifest , opts ValidationOpts ) (* AppExtraConfig , error ) {
2248 switch manifest .TEE {
2349 case buildRofl .TEETypeSGX :
2450 if manifest .Kind != buildRofl .AppKindRaw {
25- return fmt .Errorf ("unsupported app kind for SGX TEE: %s" , manifest .Kind )
51+ return nil , fmt .Errorf ("unsupported app kind for SGX TEE: %s" , manifest .Kind )
2652 }
2753 case buildRofl .TEETypeTDX :
2854 switch manifest .Kind {
@@ -38,42 +64,45 @@ func validateApp(manifest *buildRofl.Manifest) error {
3864 }
3965 }
4066 if composeAf == nil {
41- return fmt .Errorf ("missing compose.yaml artifact" )
67+ return nil , fmt .Errorf ("missing compose.yaml artifact" )
4268 }
4369
4470 // Only fetch the compose.yaml artifact.
4571 artifacts := tdxFetchArtifacts ([]* artifact {composeAf })
4672
4773 // Validate compose.yaml.
48- err := validateComposeFile (artifacts [artifactContainerCompose ], manifest )
74+ appCfg , err := validateComposeFile (artifacts [artifactContainerCompose ], manifest , opts )
4975 if err != nil {
50- return fmt .Errorf ("compose file validation failed: %w" , err )
76+ return nil , fmt .Errorf ("compose file validation failed: %w" , err )
5177 }
78+ return appCfg , nil
5279 default :
53- return fmt .Errorf ("unsupported app kind for TDX TEE: %s" , manifest .Kind )
80+ return nil , fmt .Errorf ("unsupported app kind for TDX TEE: %s" , manifest .Kind )
5481 }
5582 default :
56- return fmt .Errorf ("unsupported TEE kind: %s" , manifest .TEE )
83+ return nil , fmt .Errorf ("unsupported TEE kind: %s" , manifest .TEE )
5784 }
5885
59- return nil
86+ return & AppExtraConfig {}, nil
6087}
6188
6289// validateComposeFile validates the Docker compose file.
63- func validateComposeFile (composeFile string , manifest * buildRofl.Manifest ) error { //nolint: gocyclo
90+ func validateComposeFile (composeFile string , manifest * buildRofl.Manifest , opts ValidationOpts ) ( * AppExtraConfig , error ) { //nolint: gocyclo
6491 // Parse the compose file.
6592 options , err := compose .NewProjectOptions ([]string {composeFile }, compose .WithInterpolation (false ))
6693 if err != nil {
67- return fmt .Errorf ("failed to set-up compose options: %w" , err )
94+ return nil , fmt .Errorf ("failed to set-up compose options: %w" , err )
6895 }
6996 proj , err := options .LoadProject (context .Background ())
7097 if err != nil {
71- return fmt .Errorf ("parsing: %w" , err )
98+ return nil , fmt .Errorf ("parsing: %w" , err )
7299 }
73100
74101 // Keep track of all images encountered, as we will need them in later steps.
75102 images := []string {}
103+ customDomains := make (map [string ]struct {})
76104
105+ var appCfg AppExtraConfig
77106 for serviceName , service := range proj .Services {
78107 image := service .Image
79108 images = append (images , image )
@@ -83,21 +112,21 @@ func validateComposeFile(composeFile string, manifest *buildRofl.Manifest) error
83112 validationFailedErr := fmt .Errorf ("image '%s' of service '%s' is not a fully-qualified domain name" , image , serviceName )
84113
85114 if ! strings .Contains (image , "/" ) {
86- return validationFailedErr
115+ return nil , validationFailedErr
87116 }
88117 s := strings .Split (image , "/" )
89118 if len (s [0 ]) == 0 || len (s [1 ]) == 0 {
90- return validationFailedErr
119+ return nil , validationFailedErr
91120 }
92121
93122 domain := s [0 ]
94123 if ! strings .Contains (domain , "." ) {
95- return validationFailedErr
124+ return nil , validationFailedErr
96125 }
97126
98127 _ , err := idna .Lookup .ToASCII (domain )
99128 if err != nil {
100- return validationFailedErr
129+ return nil , validationFailedErr
101130 }
102131
103132 // Also, if any volumes are set, make sure that the source of each volume
@@ -132,23 +161,46 @@ func validateComposeFile(composeFile string, manifest *buildRofl.Manifest) error
132161 }
133162 }
134163
135- return fmt .Errorf ("volume '%s:%s' of service '%s' has an invalid external source (should be '/run/rofl-appd.sock', '/run/podman.sock' or reside inside '/storage/')" , vol .Source , vol .Target , serviceName )
164+ return nil , fmt .Errorf ("volume '%s:%s' of service '%s' has an invalid external source (should be '/run/rofl-appd.sock', '/run/podman.sock' or reside inside '/storage/')" , vol .Source , vol .Target , serviceName )
136165 }
137166
138167 // Validate ports.
139168 publishedPorts := make (map [uint64 ]struct {})
140169 for _ , port := range service .Ports {
141170 if port .Target == 0 {
142- return fmt .Errorf ("service '%s' has an invalid zero port defined" , serviceName )
171+ return nil , fmt .Errorf ("service '%s' has an invalid zero port defined" , serviceName )
143172 }
144173 if port .Published == "" || port .Published == "0" {
145- return fmt .Errorf ("port '%d' of service '%s' does not have an explicit published port defined" , port .Target , serviceName )
174+ return nil , fmt .Errorf ("port '%d' of service '%s' does not have an explicit published port defined" , port .Target , serviceName )
146175 }
147176 publishedPort , err := strconv .ParseUint (port .Published , 10 , 16 )
148177 if err != nil {
149- return fmt .Errorf ("published port '%s' of service '%s' is invalid: %w" , port .Published , serviceName , err )
178+ return nil , fmt .Errorf ("published port '%s' of service '%s' is invalid: %w" , port .Published , serviceName , err )
150179 }
151180 publishedPorts [publishedPort ] = struct {}{}
181+
182+ if port .Protocol != "tcp" {
183+ continue
184+ }
185+ proxyMode := service .Annotations [fmt .Sprintf ("net.oasis.proxy.ports.%s.mode" , port .Published )]
186+ if proxyMode == "ignore" {
187+ continue
188+ }
189+ genericDomain := fmt .Sprintf ("p%s" , port .Published )
190+ customDomain := service .Annotations [fmt .Sprintf ("net.oasis.proxy.ports.%s.custom_domain" , port .Published )]
191+ if customDomain != "" {
192+ if _ , ok := customDomains [customDomain ]; ok {
193+ return nil , fmt .Errorf ("custom domain '%s' of service '%s' is already in use" , customDomain , serviceName )
194+ }
195+ customDomains [customDomain ] = struct {}{}
196+ }
197+ appCfg .Ports = append (appCfg .Ports , & PortMapping {
198+ ServiceName : serviceName ,
199+ Port : port .Published ,
200+ ProxyMode : proxyMode ,
201+ GenericDomain : genericDomain ,
202+ CustomDomain : customDomain ,
203+ })
152204 }
153205
154206 // Validate annotations.
@@ -158,10 +210,10 @@ func validateComposeFile(composeFile string, manifest *buildRofl.Manifest) error
158210 case strings .HasPrefix (key , "net.oasis.proxy.ports" ):
159211 port , err := strconv .ParseUint (keyAtoms [4 ], 10 , 16 )
160212 if err != nil {
161- return fmt .Errorf ("proxy port annotation of service '%s' has an invalid port: %s" , serviceName , keyAtoms [4 ])
213+ return nil , fmt .Errorf ("proxy port annotation of service '%s' has an invalid port: %s" , serviceName , keyAtoms [4 ])
162214 }
163215 if _ , ok := publishedPorts [port ]; ! ok {
164- return fmt .Errorf ("proxy port annotation of service '%s' references an unpublished port '%d'" , serviceName , port )
216+ return nil , fmt .Errorf ("proxy port annotation of service '%s' references an unpublished port '%d'" , serviceName , port )
165217 }
166218
167219 // Validate supported annotations.
@@ -173,8 +225,9 @@ func validateComposeFile(composeFile string, manifest *buildRofl.Manifest) error
173225 case "passthrough" :
174226 case "terminate-tls" :
175227 default :
176- return fmt .Errorf ("proxy port annotation of service '%s', port '%d' has an invalid mode: %s" , serviceName , port , value )
228+ return nil , fmt .Errorf ("proxy port annotation of service '%s', port '%d' has an invalid mode: %s" , serviceName , port , value )
177229 }
230+ case "custom_domain" :
178231 default :
179232 }
180233 default :
@@ -184,7 +237,7 @@ func validateComposeFile(composeFile string, manifest *buildRofl.Manifest) error
184237
185238 // Make sure that we have images.
186239 if len (images ) == 0 {
187- return fmt .Errorf ("no images defined" )
240+ return nil , fmt .Errorf ("no images defined" )
188241 }
189242
190243 // Make sure that the total size of images fits into the storage size
@@ -214,25 +267,29 @@ func validateComposeFile(composeFile string, manifest *buildRofl.Manifest) error
214267 }
215268 }
216269
270+ if opts .Offline {
271+ continue
272+ }
273+
217274 // Fetch manifest from OCI repository.
218275 repo , err := remote .NewRepository (image )
219276 if err != nil {
220- return err
277+ return nil , err
221278 }
222279 mainDescriptor , mfRaw , err := oras .FetchBytes (context .Background (), repo , tag , oras .DefaultFetchBytesOptions )
223280 if err != nil {
224- return fmt .Errorf ("unable to fetch manifest for image '%s': %w" , imageFull , err )
281+ return nil , fmt .Errorf ("unable to fetch manifest for image '%s': %w" , imageFull , err )
225282 }
226283 var mf ocispec.Manifest
227284 if err = json .Unmarshal (mfRaw , & mf ); err != nil {
228- return fmt .Errorf ("unable to parse manifest for image '%s': %w" , imageFull , err )
285+ return nil , fmt .Errorf ("unable to parse manifest for image '%s': %w" , imageFull , err )
229286 }
230287
231288 // Validate platform if given.
232289 for _ , platform := range []* ocispec.Platform {mainDescriptor .Platform , mf .Config .Platform } {
233290 if platform != nil {
234291 if platform .Architecture != "amd64" || platform .OS != "linux" {
235- return fmt .Errorf ("image '%s' has incorrect platform (expected linux/amd64, got %s/%s)" , imageFull , platform .OS , platform .Architecture )
292+ return nil , fmt .Errorf ("image '%s' has incorrect platform (expected linux/amd64, got %s/%s)" , imageFull , platform .OS , platform .Architecture )
236293 }
237294 }
238295 }
@@ -254,8 +311,8 @@ func validateComposeFile(composeFile string, manifest *buildRofl.Manifest) error
254311 // We could terminate early above, but it's more useful to give the user an estimate
255312 // of how big the storage should be.
256313 if totalSize > maxSize {
257- return fmt .Errorf ("estimated total size of images (%d MB) exceeds storage size set in ROFL manifest (%d MB)" , totalSize / 1024 / 1024 , manifest .Resources .Storage .Size )
314+ return nil , fmt .Errorf ("estimated total size of images (%d MB) exceeds storage size set in ROFL manifest (%d MB)" , totalSize / 1024 / 1024 , manifest .Resources .Storage .Size )
258315 }
259316
260- return nil
317+ return & appCfg , nil
261318}
0 commit comments