@@ -23,10 +23,12 @@ var (
2323var convertCmd = & cobra.Command {
2424 Use : "convert" ,
2525 Short : "Convert configuration from other formats to kuba.yaml" ,
26- Long : `Convert configuration files from other formats (e.g., dotenv) to kuba.yaml format.
26+ Long : `Convert configuration files from other formats (e.g., dotenv, ksvc ) to kuba.yaml format.
2727
2828This command helps migrate existing configurations to kuba.yaml format.
2929For dotenv files, it will create environment variable entries using the 'value' field.
30+ For ksvc files, it will convert container env definitions (including secret refs)
31+ into kuba.yaml env mappings.
3032
3133Note: When updating an existing kuba.yaml file, comments within the modified
3234environment section will be lost as the section is regenerated. This is a limitation
@@ -40,7 +42,7 @@ comments are important.`,
4042}
4143
4244func init () {
43- convertCmd .Flags ().StringVar (& convertFrom , "from" , "" , "Source format (e.g., 'dotenv')" )
45+ convertCmd .Flags ().StringVar (& convertFrom , "from" , "" , "Source format (e.g., 'dotenv', 'ksvc' )" )
4446 convertCmd .Flags ().StringVarP (& convertEnv , "env" , "e" , "default" , "Environment name to use in kuba.yaml (default: default)" )
4547 convertCmd .Flags ().StringVar (& convertInfile , "infile" , "" , "Input file path (e.g., .env.example)" )
4648 convertCmd .Flags ().StringVar (& convertOutfile , "outfile" , "" , "Output kuba.yaml file path (default: kuba.yaml in current directory)" )
@@ -54,8 +56,8 @@ func init() {
5456func runConvert () error {
5557 logger := log .NewLogger ()
5658
57- if convertFrom != "dotenv" {
58- return fmt .Errorf ("unsupported source format: %s (only 'dotenv' is currently supported )" , convertFrom )
59+ if convertFrom != "dotenv" && convertFrom != "ksvc" {
60+ return fmt .Errorf ("unsupported source format: %s (supported: 'dotenv', 'ksvc' )" , convertFrom )
5961 }
6062
6163 // Determine output file path
@@ -64,15 +66,59 @@ func runConvert() error {
6466 outPath = "kuba.yaml"
6567 }
6668
67- logger .Debug ("Converting dotenv to kuba.yaml" , "infile" , convertInfile , "outfile" , outPath , "env" , convertEnv )
69+ logger .Debug ("Converting configuration to kuba.yaml" , "infile" , convertInfile , "outfile" , outPath , "env" , convertEnv , "from" , convertFrom )
6870
69- // Read and parse dotenv file
70- logger .Debug ("Reading dotenv file" , "path" , convertInfile )
71- envVars , err := parseDotenvFile (convertInfile )
72- if err != nil {
73- return fmt .Errorf ("failed to parse dotenv file: %w" , err )
71+ // Read and parse input file based on source format
72+ var (
73+ newEnvItems map [string ]config.EnvItem
74+ sourceVarsCount int
75+ defaultProvider string
76+ defaultProject string
77+ )
78+
79+ switch convertFrom {
80+ case "dotenv" :
81+ logger .Debug ("Reading dotenv file" , "path" , convertInfile )
82+ envVars , err := parseDotenvFile (convertInfile )
83+ if err != nil {
84+ return fmt .Errorf ("failed to parse dotenv file: %w" , err )
85+ }
86+ logger .Debug ("Parsed dotenv file" , "variables_count" , len (envVars ))
87+
88+ newEnvItems = make (map [string ]config.EnvItem , len (envVars ))
89+ for key , value := range envVars {
90+ // Skip empty values
91+ if strings .TrimSpace (value ) == "" {
92+ logger .Debug ("Skipping empty environment variable" , "key" , key )
93+ continue
94+ }
95+ newEnvItems [key ] = config.EnvItem {
96+ Value : value ,
97+ }
98+ }
99+ sourceVarsCount = len (newEnvItems )
100+ // For dotenv we default to local provider
101+ defaultProvider = "local"
102+ defaultProject = ""
103+ case "ksvc" :
104+ logger .Debug ("Reading ksvc file" , "path" , convertInfile )
105+ items , provider , project , err := parseKsvcFile (convertInfile )
106+ if err != nil {
107+ return fmt .Errorf ("failed to parse ksvc file: %w" , err )
108+ }
109+ logger .Debug ("Parsed ksvc file" , "variables_count" , len (items ), "provider" , provider , "project" , project )
110+
111+ newEnvItems = items
112+ sourceVarsCount = len (newEnvItems )
113+ // For ksvc we default to gcp provider with project/namespace if present
114+ if provider == "" {
115+ provider = "gcp"
116+ }
117+ defaultProvider = provider
118+ defaultProject = project
119+ default :
120+ return fmt .Errorf ("unsupported source format: %s (supported: 'dotenv', 'ksvc')" , convertFrom )
74121 }
75- logger .Debug ("Parsed dotenv file" , "variables_count" , len (envVars ))
76122
77123 // Load existing kuba.yaml if it exists, or create new config
78124 var kubaConfig * config.KubaConfig
@@ -107,10 +153,10 @@ func runConvert() error {
107153 env , exists := kubaConfig .Environments [convertEnv ]
108154 if ! exists {
109155 logger .Debug ("Creating new environment" , "env" , convertEnv )
110- // Create a new environment with local provider (since we're using values)
156+ // Create a new environment
111157 env = config.Environment {
112- Provider : "local" ,
113- Project : "" ,
158+ Provider : defaultProvider ,
159+ Project : defaultProject ,
114160 Env : make (map [string ]config.EnvItem ),
115161 }
116162 } else {
@@ -120,21 +166,10 @@ func runConvert() error {
120166 }
121167 }
122168
123- // Add dotenv entries to the environment
124- // Since dotenv files contain actual values, we'll use the 'value' field
125- // If the environment uses a different provider, we'll keep that but still add values
126- // The user can later convert values to secrets if needed
127- // Skip empty values - they should not be included in the config
128- for key , value := range envVars {
129- // Skip empty values
130- if strings .TrimSpace (value ) == "" {
131- logger .Debug ("Skipping empty environment variable" , "key" , key )
132- continue
133- }
134- env .Env [key ] = config.EnvItem {
135- Value : value ,
136- }
137- logger .Debug ("Added environment variable" , "key" , key )
169+ // Add or update entries in the environment
170+ for key , item := range newEnvItems {
171+ env .Env [key ] = item
172+ logger .Debug ("Added or updated environment variable" , "key" , key )
138173 }
139174
140175 // Clean up empty values from the environment before writing
@@ -149,11 +184,99 @@ func runConvert() error {
149184 return fmt .Errorf ("failed to write kuba.yaml: %w" , err )
150185 }
151186
152- fmt .Printf ("Successfully converted %d variables from %s to kuba.yaml (environment: %s)\n " , len ( envVars ) , convertInfile , convertEnv )
187+ fmt .Printf ("Successfully converted %d variables from %s to kuba.yaml (environment: %s)\n " , sourceVarsCount , convertInfile , convertEnv )
153188 logger .Debug ("Conversion completed successfully" )
154189 return nil
155190}
156191
192+ // parseKsvcFile reads and parses a Knative Service (ksvc) YAML file and converts
193+ // its container environment variables into kuba EnvItems.
194+ // It returns the env items, along with a suggested default provider and project.
195+ func parseKsvcFile (filePath string ) (map [string ]config.EnvItem , string , string , error ) {
196+ type ksvcEnv struct {
197+ Name string `yaml:"name"`
198+ Value string `yaml:"value,omitempty"`
199+ ValueFrom struct {
200+ SecretKeyRef struct {
201+ Name string `yaml:"name"`
202+ Key string `yaml:"key"`
203+ } `yaml:"secretKeyRef"`
204+ } `yaml:"valueFrom,omitempty"`
205+ }
206+
207+ type ksvcContainer struct {
208+ Env []ksvcEnv `yaml:"env"`
209+ }
210+
211+ type ksvcSpecTemplateSpec struct {
212+ Containers []ksvcContainer `yaml:"containers"`
213+ }
214+
215+ type ksvcSpecTemplate struct {
216+ Spec ksvcSpecTemplateSpec `yaml:"spec"`
217+ }
218+
219+ type ksvcSpec struct {
220+ Template ksvcSpecTemplate `yaml:"template"`
221+ }
222+
223+ type ksvcMetadata struct {
224+ Namespace string `yaml:"namespace"`
225+ }
226+
227+ type ksvcRoot struct {
228+ Metadata ksvcMetadata `yaml:"metadata"`
229+ Spec ksvcSpec `yaml:"spec"`
230+ }
231+
232+ data , err := os .ReadFile (filePath )
233+ if err != nil {
234+ return nil , "" , "" , fmt .Errorf ("failed to read file: %w" , err )
235+ }
236+
237+ var svc ksvcRoot
238+ if err := yaml .Unmarshal (data , & svc ); err != nil {
239+ return nil , "" , "" , fmt .Errorf ("failed to unmarshal ksvc yaml: %w" , err )
240+ }
241+
242+ envItems := make (map [string ]config.EnvItem )
243+
244+ // Iterate all containers and their env vars; last one wins on duplicates
245+ for _ , container := range svc .Spec .Template .Spec .Containers {
246+ for _ , e := range container .Env {
247+ if e .Name == "" {
248+ continue
249+ }
250+
251+ // Hard-coded value
252+ if strings .TrimSpace (e .Value ) != "" {
253+ envItems [e .Name ] = config.EnvItem {
254+ Value : e .Value ,
255+ }
256+ continue
257+ }
258+
259+ // Secret reference
260+ if e .ValueFrom .SecretKeyRef .Name != "" {
261+ // We treat the Kubernetes secret name as the secret-key identifier.
262+ // The key (often "latest") typically represents the version and is
263+ // intentionally not modeled here; providers usually default to latest.
264+ envItems [e .Name ] = config.EnvItem {
265+ SecretKey : e .ValueFrom .SecretKeyRef .Name ,
266+ }
267+ continue
268+ }
269+ }
270+ }
271+
272+ // Suggested provider/project defaults for the created environment.
273+ // For Cloud Run/Knative on GCP the namespace is typically the project number.
274+ suggestedProvider := "gcp"
275+ suggestedProject := strings .TrimSpace (svc .Metadata .Namespace )
276+
277+ return envItems , suggestedProvider , suggestedProject , nil
278+ }
279+
157280// parseDotenvFile reads and parses a dotenv file
158281// It handles:
159282// - Comments (lines starting with #)
0 commit comments