Commit f9a8fe4a authored by Muhammad Umair Khan's avatar Muhammad Umair Khan
Browse files

Add scenario import optimization, meepctl host validation, automated UID/GID...

Add scenario import optimization, meepctl host validation, automated UID/GID config, and colorized Go application logging
parent 1a10233c
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -235,6 +235,38 @@ func ConfigValidate(filePath string) (valid bool) {
			fmt.Println("")
			configValid = false
		}

		// Validate host UID & GID match .meepctl-repocfg.yaml
		configUid := RepoCfg.GetString("repo.deployment.permissions.uid")
		configGid := RepoCfg.GetString("repo.deployment.permissions.gid")

		hostUid := os.Getenv("SUDO_UID")
		if hostUid == "" {
			hostUid = strconv.Itoa(os.Getuid())
		}
		hostGid := os.Getenv("SUDO_GID")
		if hostGid == "" {
			hostGid = strconv.Itoa(os.Getgid())
		}

		if configUid != "" && configUid != hostUid {
			fmt.Println("")
			fmt.Println(FormatError("  ERROR      invalid meepctl config: repo.deployment.permissions.uid mismatch"))
			fmt.Println("             Host user UID:  " + hostUid)
			fmt.Println("             Configured UID: " + configUid)
			fmt.Println("             Fix: Update 'repo.deployment.permissions.uid' to match host UID (" + hostUid + ") in .meepctl-repocfg.yaml")
			fmt.Println("")
			configValid = false
		}
		if configGid != "" && configGid != hostGid {
			fmt.Println("")
			fmt.Println(FormatError("  ERROR      invalid meepctl config: repo.deployment.permissions.gid mismatch"))
			fmt.Println("             Host user GID:  " + hostGid)
			fmt.Println("             Configured GID: " + configGid)
			fmt.Println("             Fix: Update 'repo.deployment.permissions.gid' to match host GID (" + hostGid + ") in .meepctl-repocfg.yaml")
			fmt.Println("")
			configValid = false
		}
	}

	return configValid
+84 −9
Original line number Diff line number Diff line
@@ -18,8 +18,11 @@ package logger

import (
	"fmt"
	"os"
	"path"
	"runtime"
	"sort"
	"strings"

	logrus "github.com/sirupsen/logrus"
)
@@ -30,21 +33,93 @@ const LoggerTimeStampFormat = "2006-01-02T15:04:05.999Z07:00"

type Fields map[string]interface{}

// ANSI color escape codes
const (
	ColorReset  = "\033[0m"
	ColorRed    = "\033[31m"
	ColorGreen  = "\033[32m"
	ColorYellow = "\033[33m"
	ColorBlue   = "\033[34m"
	ColorPurple = "\033[35m"
	ColorCyan   = "\033[36m"
	ColorWhite  = "\033[37m"
	ColorBold   = "\033[1m"
)

type MeepFormatter struct {
	component string
}

func (f *MeepFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var levelColor string
	switch entry.Level {
	case logrus.InfoLevel:
		levelColor = ColorGreen
	case logrus.WarnLevel:
		levelColor = ColorYellow
	case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
		levelColor = ColorBold + ColorRed
	case logrus.DebugLevel:
		levelColor = ColorCyan
	case logrus.TraceLevel:
		levelColor = ColorWhite
	default:
		levelColor = ColorReset
	}

	timestamp := entry.Time.Format(LoggerTimeStampFormat)
	levelStr := strings.ToUpper(entry.Level.String())

	var fieldsStr string
	if len(entry.Data) > 0 {
		fields := make([]string, 0, len(entry.Data))
		for k, v := range entry.Data {
			if k == "meep.component" || k == "meep.from" {
				continue
			}
			fields = append(fields, fmt.Sprintf("%s=%v", k, v))
		}
		if len(fields) > 0 {
			sort.Strings(fields)
			fieldsStr = fmt.Sprintf(" %s%s%s", ColorPurple, strings.Join(fields, " "), ColorReset)
		}
	}

	fromStr := ""
	if from, ok := entry.Data["meep.from"]; ok {
		fromStr = fmt.Sprintf(" (%s%s%s)", ColorBlue, from, ColorReset)
	}

	logLine := fmt.Sprintf("%s %s[%s]%s %s%s%s%s - %s%s\n",
		timestamp,
		ColorBold+ColorBlue, f.component, ColorReset,
		levelColor, levelStr, ColorReset,
		fromStr,
		entry.Message,
		fieldsStr,
	)

	return []byte(logLine), nil
}

func MeepTextLogInit(name string) {
	Formatter := new(logrus.TextFormatter)
	Formatter.TimestampFormat = LoggerTimeStampFormat
	Formatter.FullTimestamp = true
	logrus.SetFormatter(Formatter)
	//logrus.SetLevel(logrus.TraceLevel)
	logrus.SetLevel(logrus.DebugLevel)
	componentName = name
	if os.Getenv("MEEP_LOG_FORMAT") == "json" {
		logrus.SetFormatter(&logrus.JSONFormatter{})
	} else {
		logrus.SetFormatter(&MeepFormatter{component: name})
	}
	logrus.SetLevel(logrus.DebugLevel)
}

func MeepJSONLogInit(name string) {
	componentName = name
	if os.Getenv("MEEP_LOG_FORMAT") == "json" {
		logrus.SetFormatter(&logrus.JSONFormatter{})
	//logrus.SetLevel(logrus.TraceLevel)
	} else {
		logrus.SetFormatter(&MeepFormatter{component: name})
	}
	logrus.SetLevel(logrus.DebugLevel)
	componentName = name
}

// getLogCaller
+40 −61
Original line number Diff line number Diff line
# yaml-language-server: $schema=none
---
# ============================================================
# MEC Sandbox Configuration
@@ -26,20 +27,20 @@
    dest: /usr/local/share/ca-certificates/kubernetes-ca.crt
    remote_src: true
    mode: "0644"
  register: ca_cert_copy

- name: Update system CA certificates
  command: update-ca-certificates
  changed_when: true
  when: ca_cert_copy.changed

- name: Restart docker daemon
- name: Restart container runtimes on certificate update
  systemd:
    name: docker
    state: restarted

- name: Restart containerd daemon
  systemd:
    name: containerd
    name: "{{ item }}"
    state: restarted
  loop:
    - docker
    - containerd
  when: ca_cert_copy.changed

- name: Verify etsi-mec-sandbox directory exists
  stat:
@@ -67,58 +68,6 @@
      must be siblings under the same parent directory (e.g. ~/etsi-mec-sandbox and ~/etsi-mec-sandbox-frontend).
  when: not frontend_dir_check.stat.exists

# # --- Patch chart values: runAsUser/fsGroup 1001 → 1000 ---

# - name: "Patch postgis chart — fsGroup 1001 → 1000"
#   replace:
#     path: "{{ mec_sandbox_dir }}/charts/postgis/values.yaml"
#     regexp: 'fsGroup:\s*1001'
#     replace: 'fsGroup: 1000'

# - name: "Patch postgis chart — runAsUser 1001 → 1000"
#   replace:
#     path: "{{ mec_sandbox_dir }}/charts/postgis/values.yaml"
#     regexp: 'runAsUser:\s*1001'
#     replace: 'runAsUser: 1000'

# - name: "Patch redis chart — fsGroup 1001 → 1000"
#   replace:
#     path: "{{ mec_sandbox_dir }}/charts/redis/values.yaml"
#     regexp: 'fsGroup:\s*1001'
#     replace: 'fsGroup: 1000'

# - name: "Patch redis chart — runAsUser 1001 → 1000"
#   replace:
#     path: "{{ mec_sandbox_dir }}/charts/redis/values.yaml"
#     regexp: 'runAsUser:\s*1001'
#     replace: 'runAsUser: 1000'

# - name: "Patch docker-registry chart — fsGroup 1001 → 1000"
#   replace:
#     path: "{{ mec_sandbox_dir }}/charts/docker-registry/values.yaml"
#     regexp: 'fsGroup:\s*1001'
#     replace: 'fsGroup: 1000'

# - name: "Patch docker-registry chart — runAsUser 1001 → 1000"
#   replace:
#     path: "{{ mec_sandbox_dir }}/charts/docker-registry/values.yaml"
#     regexp: 'runAsUser:\s*1001'
#     replace: 'runAsUser: 1000'

# # --- Patch .meepctl-repocfg.yaml permissions uid/gid ---

# - name: "Patch repocfg — uid 1001 → 1000"
#   replace:
#     path: "{{ mec_frontend_dir }}/config/.meepctl-repocfg.yaml"
#     regexp: 'uid:\s*1001'
#     replace: 'uid: 1000'

# - name: "Patch repocfg — gid 1001 → 1000"
#   replace:
#     path: "{{ mec_frontend_dir }}/config/.meepctl-repocfg.yaml"
#     regexp: 'gid:\s*1001'
#     replace: 'gid: 1000'

# --- Update secrets.yaml with user-provided GitHub OAuth ---

- name: "Update GitHub OAuth client-id in secrets.yaml"
@@ -147,13 +96,43 @@
    regexp: 'redirect-uri:\s*https://(mec-platform|try-mec)\.etsi\.org/platform-ctrl/v1/authorize'
    replace: "redirect-uri: https://{{ mec_host_address }}/platform-ctrl/v1/authorize"

- name: Get UID of target_user
  command: "id -u {{ target_user }}"
  register: target_uid_check
  changed_when: false

- name: Get GID of target_user
  command: "id -g {{ target_user }}"
  register: target_gid_check
  changed_when: false

- name: Update UID in .meepctl-repocfg.yaml
  replace:
    path: "{{ item }}"
    regexp: 'uid:\s*\d+'
    replace: "uid: {{ target_uid_check.stdout }}"
  loop:
    - "{{ mec_sandbox_dir }}/.meepctl-repocfg.yaml"
    - "{{ mec_frontend_dir }}/config/.meepctl-repocfg.yaml"
  ignore_errors: true

- name: Update GID in .meepctl-repocfg.yaml
  replace:
    path: "{{ item }}"
    regexp: 'gid:\s*\d+'
    replace: "gid: {{ target_gid_check.stdout }}"
  loop:
    - "{{ mec_sandbox_dir }}/.meepctl-repocfg.yaml"
    - "{{ mec_frontend_dir }}/config/.meepctl-repocfg.yaml"
  ignore_errors: true

- name: Pre-create .meep directories with correct ownership
  file:
    path: "{{ item }}"
    state: directory
    owner: "{{ target_user }}"
    group: "{{ target_user }}"
    mode: '0755'
    mode: "0755"
  loop:
    - "{{ target_home }}/.meep"
    - "{{ target_home }}/.meep/postgis"
+33 −86
Original line number Diff line number Diff line
# yaml-language-server: $schema=none
---
# ============================================================
# MEC Sandbox Deployment
@@ -129,8 +130,8 @@

# --- Deploy dependencies (with retries, ignoring thanos/prometheus failures) ---

- name: "Deploy dependencies (meepctl deploy dep)"
  shell: "meepctl deploy dep all 2>&1 | tee /tmp/meepctl_deploy_dep.log"
- name: Deploy dependencies (meepctl deploy dep)
  shell: "meepctl deploy dep all -f 2>&1 | tee /tmp/meepctl_deploy_dep.log"
  args:
    executable: /bin/bash
    chdir: "{{ mec_sandbox_dir }}"
@@ -141,86 +142,29 @@
    HOME: "{{ target_home }}"
    KUBECONFIG: "{{ target_home }}/.kube/config"
  register: dep_deploy
  until: >
    dep_deploy.rc == 0 or
    'thanos' in (dep_deploy.stderr | default('') | lower) or
    'prometheus' in (dep_deploy.stderr | default('') | lower) or
    'thanos' in (dep_deploy.stdout | default('') | lower) or
    'prometheus' in (dep_deploy.stdout | default('') | lower)
  retries: 3
  delay: 10
  ignore_errors: true

- name: Show deploy dep output
  debug:
    msg: "{{ dep_deploy.stdout_lines | default([]) }}"

- name: "Retry deploy dep with force if failed (attempt 1 of 3)"
  shell: "meepctl deploy dep all -f 2>&1 | tee /tmp/meepctl_deploy_dep_retry1.log"
  args:
    executable: /bin/bash
    chdir: "{{ mec_sandbox_dir }}"
  become: true
  become_user: "{{ target_user }}"
  environment:
    PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin"
    HOME: "{{ target_home }}"
    KUBECONFIG: "{{ target_home }}/.kube/config"
  register: dep_deploy_retry1
  when: dep_deploy.rc != 0
  ignore_errors: true

- name: "Retry deploy dep with force if failed (attempt 2 of 3)"
  shell: "meepctl deploy dep all -f 2>&1 | tee /tmp/meepctl_deploy_dep_retry2.log"
  args:
    executable: /bin/bash
    chdir: "{{ mec_sandbox_dir }}"
  become: true
  become_user: "{{ target_user }}"
  environment:
    PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin"
    HOME: "{{ target_home }}"
    KUBECONFIG: "{{ target_home }}/.kube/config"
  register: dep_deploy_retry2
  when: dep_deploy_retry1 is defined and dep_deploy_retry1.rc is defined and dep_deploy_retry1.rc != 0
  ignore_errors: true

- name: "Retry deploy dep with force if failed (attempt 3 of 3)"
  shell: "meepctl deploy dep all -f 2>&1 | tee /tmp/meepctl_deploy_dep_retry3.log"
  args:
    executable: /bin/bash
    chdir: "{{ mec_sandbox_dir }}"
  become: true
  become_user: "{{ target_user }}"
  environment:
    PATH: "/usr/local/go/bin:{{ target_home }}/gocode/bin:/snap/bin:/usr/local/bin:/usr/bin:/bin"
    HOME: "{{ target_home }}"
    KUBECONFIG: "{{ target_home }}/.kube/config"
  register: dep_deploy_retry3
  when: dep_deploy_retry2 is defined and dep_deploy_retry2.rc is defined and dep_deploy_retry2.rc != 0
  ignore_errors: true

- name: Determine final deploy dep result
  set_fact:
    dep_final: >-
      {{
        dep_deploy_retry3 if (dep_deploy_retry3 is defined and dep_deploy_retry3.rc is defined) else
        (dep_deploy_retry2 if (dep_deploy_retry2 is defined and dep_deploy_retry2.rc is defined) else
        (dep_deploy_retry1 if (dep_deploy_retry1 is defined and dep_deploy_retry1.rc is defined) else
        dep_deploy))
      }}

- name: Check for non-thanos/prometheus failures in deploy dep
  fail:
    msg: |
      Dependency deployment failed after retries.
      Output: {{ dep_final.stdout | default('') }}
      Errors: {{ dep_final.stderr | default('') }}
      Output: {{ dep_deploy.stdout | default('') }}
      Errors: {{ dep_deploy.stderr | default('') }}
      Note: thanos and prometheus failures are expected and can be ignored.
  when: >
    dep_final.rc is defined and dep_final.rc != 0 and
    'thanos' not in (dep_final.stderr | default('') | lower) and
    'prometheus' not in (dep_final.stderr | default('') | lower) and
    'thanos' not in (dep_final.stdout | default('') | lower) and
    'prometheus' not in (dep_final.stdout | default('') | lower)

- name: "Note on deploy dep result"
  debug:
    msg: >
      Dependency deployment completed.
      If thanos/prometheus failed, that is expected and ignored.
    dep_deploy.rc is defined and dep_deploy.rc != 0 and
    'thanos' not in (dep_deploy.stderr | default('') | lower) and
    'prometheus' not in (dep_deploy.stderr | default('') | lower) and
    'thanos' not in (dep_deploy.stdout | default('') | lower) and
    'prometheus' not in (dep_deploy.stdout | default('') | lower)

# --- Build all ---

@@ -237,10 +181,12 @@
    HOME: "{{ target_home }}"
    KUBECONFIG: "{{ target_home }}/.kube/config"
  register: build_all
  when: rebuild | default(true) | bool

- name: Show build all output
  debug:
    msg: "{{ build_all.stdout_lines | default([]) }}"
  when: rebuild | default(true) | bool

# --- Dockerize all ---

@@ -257,10 +203,12 @@
    HOME: "{{ target_home }}"
    KUBECONFIG: "{{ target_home }}/.kube/config"
  register: dockerize_all
  when: rebuild | default(true) | bool

- name: Show dockerize all output
  debug:
    msg: "{{ dockerize_all.stdout_lines | default([]) }}"
  when: rebuild | default(true) | bool

# --- Prune docker images ---

@@ -269,6 +217,7 @@
  args:
    executable: /bin/bash
  become: true
  when: rebuild | default(true) | bool

# --- Deploy core ---

@@ -298,19 +247,17 @@
  environment:
    KUBECONFIG: "{{ target_home }}/.kube/config"

- name: Find all YAML network scenarios
  find:
    paths: "{{ mec_frontend_dir }}/networks"
    patterns: "*.yaml"
  register: scenario_files
  become: true
  become_user: "{{ target_user }}"

- name: Load, convert, and import scenarios to platform-ctrl API
  shell: |
    python3 -c "import sys, yaml, json; sc = yaml.safe_load(open('{{ item.path }}')); sc['name'] = '{{ item.path | basename | splitext | first }}'; print(json.dumps(sc))" | \
    curl -X POST -H "Content-Type: application/json" -d @- "http://{{ platform_ctrl_ip.stdout }}/platform-ctrl/v1/scenarios/{{ item.path | basename | splitext | first }}"
  loop: "{{ scenario_files.files }}"
  ansible.builtin.shell: |
    for f in "{{ mec_frontend_dir }}"/networks/*.yaml; do
      if [ -f "$f" ]; then
        name=$(basename "$f" .yaml)
        python3 -c "import sys, yaml, json; sc = yaml.safe_load(open('$f')); sc['name'] = '$name'; print(json.dumps(sc))" | \
        curl -s -X POST -H "Content-Type: application/json" -d @- "http://{{ platform_ctrl_ip.stdout }}/platform-ctrl/v1/scenarios/$name"
      fi
    done
  args:
    executable: /bin/bash
  become: true
  become_user: "{{ target_user }}"
  ignore_errors: true