Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package semaphore for openSUSE:Factory 
checked in at 2026-07-28 18:19:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/semaphore (Old)
 and      /work/SRC/openSUSE:Factory/.semaphore.new.2004 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "semaphore"

Tue Jul 28 18:19:55 2026 rev:54 rq:1368196 version:2.18.29

Changes:
--------
--- /work/SRC/openSUSE:Factory/semaphore/semaphore.changes      2026-07-23 
23:14:18.368627877 +0200
+++ /work/SRC/openSUSE:Factory/.semaphore.new.2004/semaphore.changes    
2026-07-28 18:21:44.694566787 +0200
@@ -1,0 +2,7 @@
+Tue Jul 28 11:25:29 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 2.18.29:
+  * fix(secrets): do not remove synchronized fields and readoly
+    fields
+
+-------------------------------------------------------------------

Old:
----
  semaphore-2.18.28.obscpio
  web-2.18.28.tar.gz

New:
----
  semaphore-2.18.29.obscpio
  web-2.18.29.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ semaphore.spec ++++++
--- /var/tmp/diff_new_pack.H4ct5J/_old  2026-07-28 18:21:50.814780340 +0200
+++ /var/tmp/diff_new_pack.H4ct5J/_new  2026-07-28 18:21:50.822780619 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           semaphore
-Version:        2.18.28
+Version:        2.18.29
 Release:        0
 Summary:        Modern UI for Ansible
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.H4ct5J/_old  2026-07-28 18:21:50.906783551 +0200
+++ /var/tmp/diff_new_pack.H4ct5J/_new  2026-07-28 18:21:50.910783690 +0200
@@ -3,7 +3,7 @@
     <param 
name="url">https://github.com/ansible-semaphore/semaphore.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v2.18.28</param>
+    <param name="revision">refs/tags/v2.18.29</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.H4ct5J/_old  2026-07-28 18:21:50.930784388 +0200
+++ /var/tmp/diff_new_pack.H4ct5J/_new  2026-07-28 18:21:50.934784528 +0200
@@ -3,6 +3,6 @@
                 <param 
name="url">https://github.com/ansible-semaphore/semaphore</param>
               <param 
name="changesrevision">8a4dcf0868af718aaa5871368a3247dd622521f4</param></service><service
 name="tar_scm">
                 <param 
name="url">https://github.com/ansible-semaphore/semaphore.git</param>
-              <param 
name="changesrevision">33874e063dd9d7dc441e11f7fbfc24c0c28b270f</param></service></servicedata>
+              <param 
name="changesrevision">91719b944ccf17e23cbf5e1955a1caea32a58c5c</param></service></servicedata>
 (No newline at EOF)
 

++++++ semaphore-2.18.28.obscpio -> semaphore-2.18.29.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/semaphore-2.18.28/api/projects/environment.go 
new/semaphore-2.18.29/api/projects/environment.go
--- old/semaphore-2.18.28/api/projects/environment.go   2026-07-22 
14:24:43.000000000 +0200
+++ new/semaphore-2.18.29/api/projects/environment.go   2026-07-28 
07:08:59.000000000 +0200
@@ -296,7 +296,6 @@
        env := helpers.GetFromContext(r, "environment").(db.Environment)
 
        err := c.environmentService.Delete(env.ProjectID, env.ID)
-       //err := helpers.Store(r).DeleteEnvironment(env.ProjectID, env.ID)
 
        if errors.Is(err, db.ErrInvalidOperation) {
                helpers.WriteJSON(w, http.StatusBadRequest, map[string]any{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/semaphore-2.18.28/cli/cmd/root.go 
new/semaphore-2.18.29/cli/cmd/root.go
--- old/semaphore-2.18.28/cli/cmd/root.go       2026-07-22 14:24:43.000000000 
+0200
+++ new/semaphore-2.18.29/cli/cmd/root.go       2026-07-28 07:08:59.000000000 
+0200
@@ -97,7 +97,7 @@
        accessKeyService := server.NewAccessKeyService(store, 
encryptionService, store)
        secretStorageService := server.NewSecretStorageService(store, store, 
accessKeyService, encryptionService)
        secretStorageSyncScheduler := 
server.NewSecretStorageSyncScheduler(store, secretStorageService)
-       environmentService := server.NewEnvironmentService(store, 
encryptionService)
+       environmentService := server.NewEnvironmentService(store, 
encryptionService, store)
        runnerService := server.NewRunnerService(store)
        subscriptionService := proServer.NewSubscriptionService(store, store, 
store, terraformStore)
        logWriteService := proServer.NewLogWriteService()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/semaphore-2.18.28/services/server/environment_svc.go 
new/semaphore-2.18.29/services/server/environment_svc.go
--- old/semaphore-2.18.28/services/server/environment_svc.go    2026-07-22 
14:24:43.000000000 +0200
+++ new/semaphore-2.18.29/services/server/environment_svc.go    2026-07-28 
07:08:59.000000000 +0200
@@ -13,16 +13,19 @@
 func NewEnvironmentService(
        environmentRepo db.EnvironmentManager,
        encryptionService AccessKeyEncryptionService,
+       secretStorageRepo db.SecretStorageRepository,
 ) EnvironmentService {
        return &EnvironmentServiceImpl{
                environmentRepo:   environmentRepo,
                encryptionService: encryptionService,
+               secretStorageRepo: secretStorageRepo,
        }
 }
 
 type EnvironmentServiceImpl struct {
        environmentRepo   db.EnvironmentManager
        encryptionService AccessKeyEncryptionService
+       secretStorageRepo db.SecretStorageRepository
 }
 
 func (s *EnvironmentServiceImpl) Delete(projectID int, environmentID int) (err 
error) {
@@ -30,6 +33,11 @@
                return fmt.Errorf("invalid project or environment ID")
        }
 
+       env, err := s.environmentRepo.GetEnvironment(projectID, environmentID)
+       if err != nil {
+               return
+       }
+
        secrets, err := s.environmentRepo.GetEnvironmentSecrets(projectID, 
environmentID)
        if err != nil {
                return
@@ -43,10 +51,23 @@
 
        var errors []error
 
-       for _, secret := range secrets {
-               err = s.encryptionService.DeleteSecret(&secret)
+       if env.SecretStorageID != nil {
+               var storage db.SecretStorage
+               storage, err = s.secretStorageRepo.GetSecretStorage(projectID, 
*env.SecretStorageID)
                if err != nil {
-                       errors = append(errors, err)
+                       return
+               }
+
+               if !storage.ReadOnly {
+                       for _, secret := range secrets {
+                               if secret.Synchronized {
+                                       continue
+                               }
+                               err = s.encryptionService.DeleteSecret(&secret)
+                               if err != nil {
+                                       errors = append(errors, err)
+                               }
+                       }
                }
        }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/semaphore-2.18.28/services/server/environment_svc_test.go 
new/semaphore-2.18.29/services/server/environment_svc_test.go
--- old/semaphore-2.18.28/services/server/environment_svc_test.go       
1970-01-01 01:00:00.000000000 +0100
+++ new/semaphore-2.18.29/services/server/environment_svc_test.go       
2026-07-28 07:08:59.000000000 +0200
@@ -0,0 +1,337 @@
+package server
+
+import (
+       "errors"
+       "testing"
+
+       "github.com/semaphoreui/semaphore/db"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+type mockEnvironmentManager struct {
+       GetEnvironmentFn        func(projectID int, environmentID int) 
(db.Environment, error)
+       GetEnvironmentSecretsFn func(projectID int, environmentID int) 
([]db.AccessKey, error)
+       DeleteEnvironmentFn     func(projectID int, environmentID int) error
+
+       DeleteEnvironmentCalls int
+}
+
+func (m *mockEnvironmentManager) GetEnvironment(projectID int, environmentID 
int) (db.Environment, error) {
+       if m.GetEnvironmentFn != nil {
+               return m.GetEnvironmentFn(projectID, environmentID)
+       }
+       return db.Environment{}, nil
+}
+
+func (m *mockEnvironmentManager) GetEnvironmentSecrets(projectID int, 
environmentID int) ([]db.AccessKey, error) {
+       if m.GetEnvironmentSecretsFn != nil {
+               return m.GetEnvironmentSecretsFn(projectID, environmentID)
+       }
+       return nil, nil
+}
+
+func (m *mockEnvironmentManager) DeleteEnvironment(projectID int, 
environmentID int) error {
+       m.DeleteEnvironmentCalls++
+       if m.DeleteEnvironmentFn != nil {
+               return m.DeleteEnvironmentFn(projectID, environmentID)
+       }
+       return nil
+}
+
+// Stub methods to satisfy db.EnvironmentManager
+func (m *mockEnvironmentManager) GetEnvironmentRefs(projectID int, 
environmentID int) (db.ObjectReferrers, error) {
+       return db.ObjectReferrers{}, nil
+}
+func (m *mockEnvironmentManager) GetEnvironments(projectID int, params 
db.RetrieveQueryParams) ([]db.Environment, error) {
+       return nil, nil
+}
+func (m *mockEnvironmentManager) UpdateEnvironment(env db.Environment) error { 
return nil }
+func (m *mockEnvironmentManager) CreateEnvironment(env db.Environment) 
(db.Environment, error) {
+       return db.Environment{}, nil
+}
+
+type mockSecretStorageRepository struct {
+       GetSecretStorageFn func(projectID int, storageID int) 
(db.SecretStorage, error)
+}
+
+func (m *mockSecretStorageRepository) GetSecretStorage(projectID int, 
storageID int) (db.SecretStorage, error) {
+       if m.GetSecretStorageFn != nil {
+               return m.GetSecretStorageFn(projectID, storageID)
+       }
+       return db.SecretStorage{}, nil
+}
+
+// Stub methods to satisfy db.SecretStorageRepository
+func (m *mockSecretStorageRepository) GetSecretStorages(projectID int) 
([]db.SecretStorage, error) {
+       return nil, nil
+}
+func (m *mockSecretStorageRepository) CreateSecretStorage(storage 
db.SecretStorage) (db.SecretStorage, error) {
+       return db.SecretStorage{}, nil
+}
+func (m *mockSecretStorageRepository) UpdateSecretStorage(storage 
db.SecretStorage) error { return nil }
+func (m *mockSecretStorageRepository) GetSecretStorageRefs(projectID int, 
storageID int) (db.ObjectReferrers, error) {
+       return db.ObjectReferrers{}, nil
+}
+func (m *mockSecretStorageRepository) DeleteSecretStorage(projectID int, 
storageID int) error {
+       return nil
+}
+
+type mockAccessKeyEncryptionService struct {
+       DeleteSecretFn func(key *db.AccessKey) error
+
+       DeletedSecretIDs []int
+}
+
+func (m *mockAccessKeyEncryptionService) DeleteSecret(key *db.AccessKey) error 
{
+       m.DeletedSecretIDs = append(m.DeletedSecretIDs, key.ID)
+       if m.DeleteSecretFn != nil {
+               return m.DeleteSecretFn(key)
+       }
+       return nil
+}
+
+// Stub methods to satisfy AccessKeyEncryptionService
+func (m *mockAccessKeyEncryptionService) SerializeSecret(key *db.AccessKey) 
error   { return nil }
+func (m *mockAccessKeyEncryptionService) DeserializeSecret(key *db.AccessKey) 
error { return nil }
+func (m *mockAccessKeyEncryptionService) FillEnvironmentSecrets(env 
*db.Environment, deserializeSecret bool) error {
+       return nil
+}
+func (m *mockAccessKeyEncryptionService) RekeyAccessKeys(oldKey string) error 
{ return nil }
+
+func TestEnvironmentServiceImpl_Delete_InvalidIDs(t *testing.T) {
+       tests := []struct {
+               name          string
+               projectID     int
+               environmentID int
+       }{
+               {"zero project ID", 0, 1},
+               {"negative project ID", -1, 1},
+               {"zero environment ID", 1, 0},
+               {"negative environment ID", 1, -1},
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       envRepo := &mockEnvironmentManager{}
+                       service := &EnvironmentServiceImpl{
+                               environmentRepo:   envRepo,
+                               encryptionService: 
&mockAccessKeyEncryptionService{},
+                               secretStorageRepo: 
&mockSecretStorageRepository{},
+                       }
+
+                       err := service.Delete(tt.projectID, tt.environmentID)
+
+                       assert.ErrorContains(t, err, "invalid project or 
environment ID")
+                       assert.Equal(t, 0, envRepo.DeleteEnvironmentCalls)
+               })
+       }
+}
+
+func TestEnvironmentServiceImpl_Delete_NoSecretStorage(t *testing.T) {
+       envRepo := &mockEnvironmentManager{
+               GetEnvironmentFn: func(projectID int, environmentID int) 
(db.Environment, error) {
+                       assert.Equal(t, 1, projectID)
+                       assert.Equal(t, 2, environmentID)
+                       return db.Environment{ID: environmentID, ProjectID: 
projectID}, nil
+               },
+               GetEnvironmentSecretsFn: func(projectID int, environmentID int) 
([]db.AccessKey, error) {
+                       return []db.AccessKey{{ID: 10}}, nil
+               },
+       }
+       encryption := &mockAccessKeyEncryptionService{}
+       service := &EnvironmentServiceImpl{
+               environmentRepo:   envRepo,
+               encryptionService: encryption,
+               secretStorageRepo: &mockSecretStorageRepository{},
+       }
+
+       err := service.Delete(1, 2)
+
+       require.NoError(t, err)
+       assert.Equal(t, 1, envRepo.DeleteEnvironmentCalls)
+       assert.Empty(t, encryption.DeletedSecretIDs)
+}
+
+func TestEnvironmentServiceImpl_Delete_DeletesUnsynchronizedSecrets(t 
*testing.T) {
+       storageID := 7
+       envRepo := &mockEnvironmentManager{
+               GetEnvironmentFn: func(projectID int, environmentID int) 
(db.Environment, error) {
+                       return db.Environment{ID: environmentID, ProjectID: 
projectID, SecretStorageID: &storageID}, nil
+               },
+               GetEnvironmentSecretsFn: func(projectID int, environmentID int) 
([]db.AccessKey, error) {
+                       return []db.AccessKey{
+                               {ID: 10},
+                               {ID: 11, Synchronized: true},
+                               {ID: 12},
+                       }, nil
+               },
+       }
+       storageRepo := &mockSecretStorageRepository{
+               GetSecretStorageFn: func(projectID int, id int) 
(db.SecretStorage, error) {
+                       assert.Equal(t, storageID, id)
+                       return db.SecretStorage{ID: id, ReadOnly: false}, nil
+               },
+       }
+       encryption := &mockAccessKeyEncryptionService{}
+       service := &EnvironmentServiceImpl{
+               environmentRepo:   envRepo,
+               encryptionService: encryption,
+               secretStorageRepo: storageRepo,
+       }
+
+       err := service.Delete(1, 2)
+
+       require.NoError(t, err)
+       assert.Equal(t, 1, envRepo.DeleteEnvironmentCalls)
+       assert.Equal(t, []int{10, 12}, encryption.DeletedSecretIDs)
+}
+
+func TestEnvironmentServiceImpl_Delete_ReadOnlyStorageKeepsSecrets(t 
*testing.T) {
+       storageID := 7
+       envRepo := &mockEnvironmentManager{
+               GetEnvironmentFn: func(projectID int, environmentID int) 
(db.Environment, error) {
+                       return db.Environment{ID: environmentID, ProjectID: 
projectID, SecretStorageID: &storageID}, nil
+               },
+               GetEnvironmentSecretsFn: func(projectID int, environmentID int) 
([]db.AccessKey, error) {
+                       return []db.AccessKey{{ID: 10}}, nil
+               },
+       }
+       storageRepo := &mockSecretStorageRepository{
+               GetSecretStorageFn: func(projectID int, id int) 
(db.SecretStorage, error) {
+                       return db.SecretStorage{ID: id, ReadOnly: true}, nil
+               },
+       }
+       encryption := &mockAccessKeyEncryptionService{}
+       service := &EnvironmentServiceImpl{
+               environmentRepo:   envRepo,
+               encryptionService: encryption,
+               secretStorageRepo: storageRepo,
+       }
+
+       err := service.Delete(1, 2)
+
+       require.NoError(t, err)
+       assert.Equal(t, 1, envRepo.DeleteEnvironmentCalls)
+       assert.Empty(t, encryption.DeletedSecretIDs)
+}
+
+func TestEnvironmentServiceImpl_Delete_AggregatesSecretDeletionErrors(t 
*testing.T) {
+       storageID := 7
+       envRepo := &mockEnvironmentManager{
+               GetEnvironmentFn: func(projectID int, environmentID int) 
(db.Environment, error) {
+                       return db.Environment{ID: environmentID, ProjectID: 
projectID, SecretStorageID: &storageID}, nil
+               },
+               GetEnvironmentSecretsFn: func(projectID int, environmentID int) 
([]db.AccessKey, error) {
+                       return []db.AccessKey{{ID: 10}, {ID: 11}}, nil
+               },
+       }
+       encryption := &mockAccessKeyEncryptionService{
+               DeleteSecretFn: func(key *db.AccessKey) error {
+                       if key.ID == 10 {
+                               return errors.New("vault unreachable")
+                       }
+                       return nil
+               },
+       }
+       service := &EnvironmentServiceImpl{
+               environmentRepo:   envRepo,
+               encryptionService: encryption,
+               secretStorageRepo: &mockSecretStorageRepository{},
+       }
+
+       err := service.Delete(1, 2)
+
+       assert.ErrorContains(t, err, "failed to delete some secrets")
+       assert.ErrorContains(t, err, "vault unreachable")
+       // The environment itself is still deleted, and all secrets are 
attempted.
+       assert.Equal(t, 1, envRepo.DeleteEnvironmentCalls)
+       assert.Equal(t, []int{10, 11}, encryption.DeletedSecretIDs)
+}
+
+func TestEnvironmentServiceImpl_Delete_RepositoryErrors(t *testing.T) {
+       storageID := 7
+
+       t.Run("GetEnvironment fails", func(t *testing.T) {
+               envRepo := &mockEnvironmentManager{
+                       GetEnvironmentFn: func(projectID int, environmentID 
int) (db.Environment, error) {
+                               return db.Environment{}, db.ErrNotFound
+                       },
+               }
+               service := &EnvironmentServiceImpl{
+                       environmentRepo:   envRepo,
+                       encryptionService: &mockAccessKeyEncryptionService{},
+                       secretStorageRepo: &mockSecretStorageRepository{},
+               }
+
+               err := service.Delete(1, 2)
+
+               assert.ErrorIs(t, err, db.ErrNotFound)
+               assert.Equal(t, 0, envRepo.DeleteEnvironmentCalls)
+       })
+
+       t.Run("GetEnvironmentSecrets fails", func(t *testing.T) {
+               envRepo := &mockEnvironmentManager{
+                       GetEnvironmentSecretsFn: func(projectID int, 
environmentID int) ([]db.AccessKey, error) {
+                               return nil, errors.New("secrets query failed")
+                       },
+               }
+               service := &EnvironmentServiceImpl{
+                       environmentRepo:   envRepo,
+                       encryptionService: &mockAccessKeyEncryptionService{},
+                       secretStorageRepo: &mockSecretStorageRepository{},
+               }
+
+               err := service.Delete(1, 2)
+
+               assert.ErrorContains(t, err, "secrets query failed")
+               assert.Equal(t, 0, envRepo.DeleteEnvironmentCalls)
+       })
+
+       t.Run("DeleteEnvironment fails", func(t *testing.T) {
+               envRepo := &mockEnvironmentManager{
+                       DeleteEnvironmentFn: func(projectID int, environmentID 
int) error {
+                               return errors.New("delete failed")
+                       },
+               }
+               encryption := &mockAccessKeyEncryptionService{}
+               service := &EnvironmentServiceImpl{
+                       environmentRepo:   envRepo,
+                       encryptionService: encryption,
+                       secretStorageRepo: &mockSecretStorageRepository{},
+               }
+
+               err := service.Delete(1, 2)
+
+               assert.ErrorContains(t, err, "delete failed")
+               assert.Empty(t, encryption.DeletedSecretIDs)
+       })
+
+       t.Run("GetSecretStorage fails", func(t *testing.T) {
+               envRepo := &mockEnvironmentManager{
+                       GetEnvironmentFn: func(projectID int, environmentID 
int) (db.Environment, error) {
+                               return db.Environment{ID: environmentID, 
ProjectID: projectID, SecretStorageID: &storageID}, nil
+                       },
+                       GetEnvironmentSecretsFn: func(projectID int, 
environmentID int) ([]db.AccessKey, error) {
+                               return []db.AccessKey{{ID: 10}}, nil
+                       },
+               }
+               storageRepo := &mockSecretStorageRepository{
+                       GetSecretStorageFn: func(projectID int, id int) 
(db.SecretStorage, error) {
+                               return db.SecretStorage{}, errors.New("storage 
not found")
+                       },
+               }
+               encryption := &mockAccessKeyEncryptionService{}
+               service := &EnvironmentServiceImpl{
+                       environmentRepo:   envRepo,
+                       encryptionService: encryption,
+                       secretStorageRepo: storageRepo,
+               }
+
+               err := service.Delete(1, 2)
+
+               assert.ErrorContains(t, err, "storage not found")
+               assert.Equal(t, 1, envRepo.DeleteEnvironmentCalls)
+               assert.Empty(t, encryption.DeletedSecretIDs)
+       })
+}

++++++ semaphore.obsinfo ++++++
--- /var/tmp/diff_new_pack.H4ct5J/_old  2026-07-28 18:21:52.538840498 +0200
+++ /var/tmp/diff_new_pack.H4ct5J/_new  2026-07-28 18:21:52.542840638 +0200
@@ -1,5 +1,5 @@
 name: semaphore
-version: 2.18.28
-mtime: 1784723083
-commit: 33874e063dd9d7dc441e11f7fbfc24c0c28b270f
+version: 2.18.29
+mtime: 1785215339
+commit: 91719b944ccf17e23cbf5e1955a1caea32a58c5c
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/semaphore/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.semaphore.new.2004/vendor.tar.gz differ: char 151, 
line 2

++++++ web-2.18.28.tar.gz -> web-2.18.29.tar.gz ++++++
/work/SRC/openSUSE:Factory/semaphore/web-2.18.28.tar.gz 
/work/SRC/openSUSE:Factory/.semaphore.new.2004/web-2.18.29.tar.gz differ: char 
13, line 1

Reply via email to