@@ -86,6 +86,17 @@ func (p *ResourceProcessor) ProcessUserCreation(userSpec types.UserSpec) (*types
8686 output .Groups = groupOutputs
8787 }
8888
89+ // 4. 创建用户级项目(不属于任何组的项目)
90+ if len (userSpec .Projects ) > 0 {
91+ log .Printf (" 创建 %d 个用户级项目...\n " , len (userSpec .Projects ))
92+ projectOutputs , err := p .createUserProjectsWithOutput (actualUsername , userSpec .Projects , nameMode )
93+ if err != nil {
94+ log .Printf (" ⚠ 创建用户级项目失败: %v\n " , err )
95+ } else {
96+ output .Projects = projectOutputs
97+ }
98+ }
99+
89100 return output , nil
90101}
91102
@@ -224,6 +235,85 @@ func (p *ResourceProcessor) ensureGroup(username string, groupSpec types.GroupSp
224235 return group .ID , group .Path , nil
225236}
226237
238+ // createUserProjectsWithOutput 创建用户级别的项目(不属于任何组)
239+ func (p * ResourceProcessor ) createUserProjectsWithOutput (username string , projects []types.ProjectSpec , userNameMode string ) ([]types.ProjectOutput , error ) {
240+ var projectOutputs []types.ProjectOutput
241+
242+ // 获取用户信息以获取用户的 namespace ID
243+ user , err := p .Client .GetUser (username )
244+ if err != nil || user == nil {
245+ return nil , fmt .Errorf ("获取用户信息失败: %w" , err )
246+ }
247+
248+ for _ , projSpec := range projects {
249+ // 确定项目的 nameMode(如果项目没有指定,则继承用户的 nameMode)
250+ projectNameMode := projSpec .NameMode
251+ if projectNameMode == "" {
252+ projectNameMode = userNameMode
253+ }
254+
255+ // 根据 nameMode 生成实际的 project path
256+ var actualProjectPath string
257+ if projectNameMode == "name" {
258+ // name 模式:直接使用配置文件中的名称
259+ actualProjectPath = projSpec .Path
260+ if actualProjectPath == "" {
261+ actualProjectPath = projSpec .Name
262+ }
263+ log .Printf (" 使用 name 模式,项目 path: %s\n " , actualProjectPath )
264+ } else {
265+ // prefix 模式:添加时间戳
266+ if projSpec .Path == "" {
267+ actualProjectPath = utils .GenerateProjectPathWithTimestamp (projSpec .Name )
268+ } else {
269+ actualProjectPath = utils .GenerateProjectPathWithTimestamp (projSpec .Path )
270+ }
271+ log .Printf (" 使用 prefix 模式,生成项目 path: %s\n " , actualProjectPath )
272+ }
273+
274+ // 用户级项目的 full path 是 username/project-path
275+ fullPath := fmt .Sprintf ("%s/%s" , username , actualProjectPath )
276+ existingProj , _ := p .Client .GetProject (fullPath )
277+
278+ var projectID int
279+ var webURL string
280+
281+ if existingProj != nil {
282+ log .Printf (" ⚠ 项目 '%s' 已存在 (ID: %d)\n " , projSpec .Name , existingProj .ID )
283+ projectID = existingProj .ID
284+ webURL = existingProj .WebURL
285+ } else {
286+ log .Printf (" 创建用户级项目: %s (path: %s)\n " , projSpec .Name , actualProjectPath )
287+ // 用户级项目使用用户的 ID 作为 namespace ID
288+ project , err := p .Client .CreateProject (
289+ username ,
290+ user .ID , // 使用用户 ID 作为 namespace ID
291+ projSpec .Name ,
292+ actualProjectPath ,
293+ projSpec .Description ,
294+ utils .GetVisibility (projSpec .Visibility ),
295+ )
296+ if err != nil {
297+ log .Printf (" ⚠ 创建项目失败 %s: %v\n " , projSpec .Name , err )
298+ continue
299+ }
300+ log .Printf (" ✓ 项目创建成功 (ID: %d, Path: %s)\n " , project .ID , project .PathWithNamespace )
301+ projectID = project .ID
302+ webURL = project .WebURL
303+ }
304+
305+ projectOutputs = append (projectOutputs , types.ProjectOutput {
306+ Name : projSpec .Name ,
307+ Path : fullPath ,
308+ ProjectID : projectID ,
309+ Description : projSpec .Description ,
310+ Visibility : projSpec .Visibility ,
311+ WebURL : webURL ,
312+ })
313+ }
314+ return projectOutputs , nil
315+ }
316+
227317// createProjectsWithOutput 创建多个项目并返回输出结果
228318func (p * ResourceProcessor ) createProjectsWithOutput (username string , groupID int , groupPath string , projects []types.ProjectSpec , groupNameMode string ) ([]types.ProjectOutput , error ) {
229319 var projectOutputs []types.ProjectOutput
@@ -314,7 +404,13 @@ func (p *ResourceProcessor) ProcessUserCleanup(userSpec types.UserSpec) error {
314404
315405 log .Printf (" 找到用户 '%s' (ID: %d, 邮箱: %s)\n " , user .Username , user .ID , user .Email )
316406
317- // 1. 删除配置文件中定义的组和项目
407+ // 1. 删除用户级项目(不属于任何组的项目)
408+ if len (userSpec .Projects ) > 0 {
409+ log .Printf (" 删除 %d 个用户级项目...\n " , len (userSpec .Projects ))
410+ p .deleteUserProjects (userSpec .Username , userSpec .Projects )
411+ }
412+
413+ // 2. 删除配置文件中定义的组和项目
318414 if len (userSpec .Groups ) > 0 {
319415 log .Printf (" 删除 %d 个组及其项目...\n " , len (userSpec .Groups ))
320416 p .deleteConfiguredGroups (userSpec .Groups )
@@ -341,6 +437,33 @@ func (p *ResourceProcessor) ProcessUserCleanup(userSpec types.UserSpec) error {
341437 return nil
342438}
343439
440+ // deleteUserProjects 删除用户级项目
441+ func (p * ResourceProcessor ) deleteUserProjects (username string , projects []types.ProjectSpec ) {
442+ for i , projSpec := range projects {
443+ log .Printf (" ------------------------------------------\n " )
444+ log .Printf (" 处理用户级项目 [%d/%d]: %s\n " , i + 1 , len (projects ), projSpec .Name )
445+
446+ // 用户级项目的 full path 是 username/project-path
447+ projectPath := projSpec .Path
448+ if projectPath == "" {
449+ projectPath = projSpec .Name
450+ }
451+ fullPath := fmt .Sprintf ("%s/%s" , username , projectPath )
452+ project , _ := p .Client .GetProject (fullPath )
453+
454+ if project != nil {
455+ log .Printf (" 删除项目: %s (ID: %d)\n " , projSpec .Name , project .ID )
456+ if err := p .Client .DeleteProject (project .ID ); err != nil {
457+ log .Printf (" ⚠ 删除项目失败: %v\n " , err )
458+ } else {
459+ log .Printf (" ✓ 项目删除成功\n " )
460+ }
461+ } else {
462+ log .Printf (" ⚠ 项目不存在,跳过: %s\n " , fullPath )
463+ }
464+ }
465+ }
466+
344467// deleteConfiguredGroups 删除配置文件中定义的组及其项目
345468func (p * ResourceProcessor ) deleteConfiguredGroups (groups []types.GroupSpec ) {
346469 for j , groupSpec := range groups {
0 commit comments