feat(swagger_v2): added support for the orca staging environment
Some checks failed
test / test (push) Failing after 47s
Some checks failed
test / test (push) Failing after 47s
This commit is contained in:
parent
6cbf963060
commit
d4102d3375
9 changed files with 186 additions and 39 deletions
20
cmd/app.go
20
cmd/app.go
|
|
@ -3,6 +3,7 @@ package cmd
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
|
@ -60,16 +61,23 @@ func newSDKClient() *edgeconnect.Client {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Build options
|
||||
opts := []edgeconnect.Option{
|
||||
edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
||||
}
|
||||
|
||||
// Add logger only if debug flag is set
|
||||
if debug {
|
||||
logger := log.New(os.Stderr, "[DEBUG] ", log.LstdFlags)
|
||||
opts = append(opts, edgeconnect.WithLogger(logger))
|
||||
}
|
||||
|
||||
if username != "" && password != "" {
|
||||
return edgeconnect.NewClientWithCredentials(baseURL, username, password,
|
||||
edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
||||
)
|
||||
return edgeconnect.NewClientWithCredentials(baseURL, username, password, opts...)
|
||||
}
|
||||
|
||||
// Fallback to no auth for now - in production should require auth
|
||||
return edgeconnect.NewClient(baseURL,
|
||||
edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
|
||||
)
|
||||
return edgeconnect.NewClient(baseURL, opts...)
|
||||
}
|
||||
|
||||
var appCmd = &cobra.Command{
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ var (
|
|||
baseURL string
|
||||
username string
|
||||
password string
|
||||
debug bool
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
|
|
@ -39,6 +40,7 @@ func init() {
|
|||
rootCmd.PersistentFlags().StringVar(&baseURL, "base-url", "", "base URL for the Edge Connect API")
|
||||
rootCmd.PersistentFlags().StringVar(&username, "username", "", "username for authentication")
|
||||
rootCmd.PersistentFlags().StringVar(&password, "password", "", "password for authentication")
|
||||
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug logging")
|
||||
|
||||
viper.BindPFlag("base_url", rootCmd.PersistentFlags().Lookup("base-url"))
|
||||
viper.BindPFlag("username", rootCmd.PersistentFlags().Lookup("username"))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// EdgeConnectConfig represents the top-level configuration structure
|
||||
|
|
@ -98,10 +100,75 @@ func (c *EdgeConnectConfig) GetImagePath() string {
|
|||
if c.Spec.IsDockerApp() && c.Spec.DockerApp.Image != "" {
|
||||
return c.Spec.DockerApp.Image
|
||||
}
|
||||
// Default for kubernetes apps
|
||||
|
||||
// For kubernetes apps, extract image from manifest
|
||||
if c.Spec.IsK8sApp() && c.Spec.K8sApp.ManifestFile != "" {
|
||||
if image, err := extractImageFromK8sManifest(c.Spec.K8sApp.ManifestFile); err == nil && image != "" {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback default for kubernetes apps
|
||||
return "https://registry-1.docker.io/library/nginx:latest"
|
||||
}
|
||||
|
||||
// extractImageFromK8sManifest extracts the container image from a Kubernetes manifest
|
||||
func extractImageFromK8sManifest(manifestPath string) (string, error) {
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read manifest: %w", err)
|
||||
}
|
||||
|
||||
// Parse multi-document YAML
|
||||
decoder := yaml.NewDecoder(strings.NewReader(string(data)))
|
||||
|
||||
for {
|
||||
var doc map[string]interface{}
|
||||
if err := decoder.Decode(&doc); err != nil {
|
||||
break // End of documents or error
|
||||
}
|
||||
|
||||
// Check if this is a Deployment
|
||||
kind, ok := doc["kind"].(string)
|
||||
if !ok || kind != "Deployment" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Navigate to spec.template.spec.containers[0].image
|
||||
spec, ok := doc["spec"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
template, ok := spec["template"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
templateSpec, ok := template["spec"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
containers, ok := templateSpec["containers"].([]interface{})
|
||||
if !ok || len(containers) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
firstContainer, ok := containers[0].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
image, ok := firstContainer["image"].(string)
|
||||
if ok && image != "" {
|
||||
return image, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no image found in Deployment manifest")
|
||||
}
|
||||
|
||||
// Validate validates metadata fields
|
||||
func (m *Metadata) Validate() error {
|
||||
if m.Name == "" {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
package edgeconnect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
sdkhttp "edp.buildth.ing/DevFW-CICD/edge-connect-client/sdk/internal/http"
|
||||
|
|
@ -164,18 +166,17 @@ func (c *Client) RefreshAppInstance(ctx context.Context, appInstKey AppInstanceK
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeleteAppInstance removes an application instance from the specified region
|
||||
// DeleteAppInstance removes an application instance
|
||||
// Maps to POST /auth/ctrl/DeleteAppInst
|
||||
func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKey, region string) error {
|
||||
transport := c.getTransport()
|
||||
url := c.BaseURL + "/api/v1/auth/ctrl/DeleteAppInst"
|
||||
|
||||
filter := AppInstanceFilter{
|
||||
AppInstance: AppInstance{Key: appInstKey},
|
||||
Region: region,
|
||||
input := DeleteAppInstanceInput{
|
||||
Key: appInstKey,
|
||||
}
|
||||
|
||||
resp, err := transport.Call(ctx, "POST", url, filter)
|
||||
resp, err := transport.Call(ctx, "POST", url, input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteAppInstance failed: %w", err)
|
||||
}
|
||||
|
|
@ -194,13 +195,29 @@ func (c *Client) DeleteAppInstance(ctx context.Context, appInstKey AppInstanceKe
|
|||
|
||||
// parseStreamingAppInstanceResponse parses the EdgeXR streaming JSON response format for app instances
|
||||
func (c *Client) parseStreamingAppInstanceResponse(resp *http.Response, result interface{}) error {
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
// Try parsing as a direct JSON array first (v2 API format)
|
||||
switch v := result.(type) {
|
||||
case *[]AppInstance:
|
||||
var appInstances []AppInstance
|
||||
if err := json.Unmarshal(bodyBytes, &appInstances); err == nil {
|
||||
*v = appInstances
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to streaming format (v1 API format)
|
||||
var appInstances []AppInstance
|
||||
var messages []string
|
||||
var hasError bool
|
||||
var errorCode int
|
||||
var errorMessage string
|
||||
|
||||
parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error {
|
||||
parseErr := sdkhttp.ParseJSONLines(io.NopCloser(bytes.NewReader(bodyBytes)), func(line []byte) error {
|
||||
// Try parsing as ResultResponse first (error format)
|
||||
var resultResp ResultResponse
|
||||
if err := json.Unmarshal(line, &resultResp); err == nil && resultResp.Result.Message != "" {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
package edgeconnect
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
|
@ -142,12 +143,12 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er
|
|||
transport := c.getTransport()
|
||||
url := c.BaseURL + "/api/v1/auth/ctrl/DeleteApp"
|
||||
|
||||
filter := AppFilter{
|
||||
App: App{Key: appKey},
|
||||
input := DeleteAppInput{
|
||||
Key: appKey,
|
||||
Region: region,
|
||||
}
|
||||
|
||||
resp, err := transport.Call(ctx, "POST", url, filter)
|
||||
resp, err := transport.Call(ctx, "POST", url, input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DeleteApp failed: %w", err)
|
||||
}
|
||||
|
|
@ -166,9 +167,27 @@ func (c *Client) DeleteApp(ctx context.Context, appKey AppKey, region string) er
|
|||
|
||||
// parseStreamingResponse parses the EdgeXR streaming JSON response format
|
||||
func (c *Client) parseStreamingResponse(resp *http.Response, result interface{}) error {
|
||||
var responses []Response[App]
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
parseErr := sdkhttp.ParseJSONLines(resp.Body, func(line []byte) error {
|
||||
// Try parsing as a direct JSON array first (v2 API format)
|
||||
switch v := result.(type) {
|
||||
case *[]App:
|
||||
var apps []App
|
||||
if err := json.Unmarshal(bodyBytes, &apps); err == nil {
|
||||
*v = apps
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to streaming format (v1 API format)
|
||||
var responses []Response[App]
|
||||
var apps []App
|
||||
var messages []string
|
||||
|
||||
parseErr := sdkhttp.ParseJSONLines(io.NopCloser(bytes.NewReader(bodyBytes)), func(line []byte) error {
|
||||
var response Response[App]
|
||||
if err := json.Unmarshal(line, &response); err != nil {
|
||||
return err
|
||||
|
|
@ -182,9 +201,6 @@ func (c *Client) parseStreamingResponse(resp *http.Response, result interface{})
|
|||
}
|
||||
|
||||
// Extract data from responses
|
||||
var apps []App
|
||||
var messages []string
|
||||
|
||||
for _, response := range responses {
|
||||
if response.HasData() {
|
||||
apps = append(apps, response.Data)
|
||||
|
|
|
|||
|
|
@ -184,24 +184,33 @@ type App struct {
|
|||
Deployment string `json:"deployment,omitempty"`
|
||||
ImageType string `json:"image_type,omitempty"`
|
||||
ImagePath string `json:"image_path,omitempty"`
|
||||
AccessPorts string `json:"access_ports,omitempty"`
|
||||
AllowServerless bool `json:"allow_serverless,omitempty"`
|
||||
DefaultFlavor Flavor `json:"defaultFlavor,omitempty"`
|
||||
ServerlessConfig interface{} `json:"serverless_config,omitempty"`
|
||||
DeploymentGenerator string `json:"deployment_generator,omitempty"`
|
||||
DeploymentManifest string `json:"deployment_manifest,omitempty"`
|
||||
RequiredOutboundConnections []SecurityRule `json:"required_outbound_connections"`
|
||||
GlobalID string `json:"global_id,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
Fields []string `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// AppInstance represents a deployed application instance
|
||||
type AppInstance struct {
|
||||
msg `json:",inline"`
|
||||
Key AppInstanceKey `json:"key"`
|
||||
AppKey AppKey `json:"app_key,omitempty"`
|
||||
Flavor Flavor `json:"flavor,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
PowerState string `json:"power_state,omitempty"`
|
||||
Fields []string `json:"fields,omitempty"`
|
||||
msg `json:",inline"`
|
||||
Key AppInstanceKey `json:"key"`
|
||||
AppKey AppKey `json:"app_key,omitempty"`
|
||||
CloudletLoc CloudletLoc `json:"cloudlet_loc,omitempty"`
|
||||
Flavor Flavor `json:"flavor,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
IngressURL string `json:"ingress_url,omitempty"`
|
||||
UniqueID string `json:"unique_id,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
PowerState string `json:"power_state,omitempty"`
|
||||
Fields []string `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// Cloudlet represents edge infrastructure
|
||||
|
|
@ -224,6 +233,12 @@ type Location struct {
|
|||
Longitude float64 `json:"longitude"`
|
||||
}
|
||||
|
||||
// CloudletLoc represents geographical coordinates for cloudlets
|
||||
type CloudletLoc struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
}
|
||||
|
||||
// Input types for API operations
|
||||
|
||||
// NewAppInput represents input for creating an application
|
||||
|
|
@ -256,6 +271,17 @@ type UpdateAppInstanceInput struct {
|
|||
AppInst AppInstance `json:"appinst"`
|
||||
}
|
||||
|
||||
// DeleteAppInput represents input for deleting an application
|
||||
type DeleteAppInput struct {
|
||||
Key AppKey `json:"key"`
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
// DeleteAppInstanceInput represents input for deleting an app instance
|
||||
type DeleteAppInstanceInput struct {
|
||||
Key AppInstanceKey `json:"key"`
|
||||
}
|
||||
|
||||
// Response wrapper types
|
||||
|
||||
// Response wraps a single API response
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
kind: edgeconnect-deployment
|
||||
metadata:
|
||||
name: "edge-app-demo" # name could be used for appName
|
||||
appVersion: "1.0.0"
|
||||
organization: "edp2"
|
||||
appVersion: "1"
|
||||
organization: "edp2-orca"
|
||||
spec:
|
||||
# dockerApp: # Docker is OBSOLETE
|
||||
# appVersion: "1.0.0"
|
||||
|
|
@ -13,10 +13,10 @@ spec:
|
|||
k8sApp:
|
||||
manifestFile: "./k8s-deployment.yaml"
|
||||
infraTemplate:
|
||||
- region: "EU"
|
||||
cloudletOrg: "TelekomOP"
|
||||
cloudletName: "Munich"
|
||||
flavorName: "EU.small"
|
||||
- region: "US"
|
||||
cloudletOrg: "TelekomOp"
|
||||
cloudletName: "gardener-shepherd-test"
|
||||
flavorName: "defualt"
|
||||
network:
|
||||
outboundConnections:
|
||||
- protocol: "tcp"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ spec:
|
|||
volumes:
|
||||
containers:
|
||||
- name: edgeconnect-coder
|
||||
image: nginx:latest
|
||||
image: edp.buildth.ing/devfw-cicd/fibonacci_pipeline:edge_platform_demo
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 80
|
||||
|
|
|
|||
|
|
@ -98,10 +98,12 @@ func NewTransport(opts RetryOptions, auth AuthProvider, logger Logger) *Transpor
|
|||
// Call executes an HTTP request with retry logic and returns typed response
|
||||
func (t *Transport) Call(ctx context.Context, method, url string, body interface{}) (*http.Response, error) {
|
||||
var reqBody io.Reader
|
||||
var jsonData []byte
|
||||
|
||||
// Marshal request body if provided
|
||||
if body != nil {
|
||||
jsonData, err := json.Marshal(body)
|
||||
var err error
|
||||
jsonData, err = json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
|
|
@ -127,8 +129,16 @@ func (t *Transport) Call(ctx context.Context, method, url string, body interface
|
|||
|
||||
// Log request
|
||||
if t.logger != nil {
|
||||
t.logger.Printf("HTTP %s %s", method, url)
|
||||
t.logger.Printf("BODY %s", reqBody)
|
||||
t.logger.Printf("=== HTTP REQUEST ===")
|
||||
t.logger.Printf("%s %s", method, url)
|
||||
if len(jsonData) > 0 {
|
||||
var prettyJSON bytes.Buffer
|
||||
if err := json.Indent(&prettyJSON, jsonData, "", " "); err == nil {
|
||||
t.logger.Printf("Request Body:\n%s", prettyJSON.String())
|
||||
} else {
|
||||
t.logger.Printf("Request Body: %s", string(jsonData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute request
|
||||
|
|
@ -139,7 +149,8 @@ func (t *Transport) Call(ctx context.Context, method, url string, body interface
|
|||
|
||||
// Log response
|
||||
if t.logger != nil {
|
||||
t.logger.Printf("HTTP %s %s -> %d", method, url, resp.StatusCode)
|
||||
t.logger.Printf("=== HTTP RESPONSE ===")
|
||||
t.logger.Printf("%s %s -> %d", method, url, resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue