Skip to content

Commit 85cbc05

Browse files
authored
Merge pull request #12 from ncode/juliano/systemd_and_cleanup
updates
2 parents 5182aca + 1cb5438 commit 85cbc05

File tree

4 files changed

+223
-7
lines changed

4 files changed

+223
-7
lines changed

cmd/cleanup.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
Copyright © 2024 Juliano Martinez <[email protected]>
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package cmd
17+
18+
import (
19+
"fmt"
20+
"github.com/hashicorp/consul/api"
21+
"github.com/ncode/tagit/pkg/tagit"
22+
"github.com/spf13/cobra"
23+
"os"
24+
)
25+
26+
// cleanupCmd represents the cleanup command
27+
var cleanupCmd = &cobra.Command{
28+
Use: "cleanup",
29+
Short: "cleanup removes all services with the tag prefix from a given consul service",
30+
Run: func(cmd *cobra.Command, args []string) {
31+
config := api.DefaultConfig()
32+
config.Address = cmd.InheritedFlags().Lookup("consul-addr").Value.String()
33+
config.Token = cmd.InheritedFlags().Lookup("token").Value.String()
34+
consulClient, err := api.NewClient(config)
35+
if err != nil {
36+
fmt.Printf("error creating consul client: %s", err.Error())
37+
os.Exit(1)
38+
}
39+
40+
t := tagit.New(
41+
tagit.NewConsulAPIWrapper(consulClient),
42+
&tagit.CmdExecutor{},
43+
cmd.InheritedFlags().Lookup("service-id").Value.String(),
44+
cmd.InheritedFlags().Lookup("script").Value.String(),
45+
0,
46+
cmd.InheritedFlags().Lookup("tag-prefix").Value.String(),
47+
)
48+
err = t.CleanupTags()
49+
if err != nil {
50+
fmt.Printf("error cleaning up tags: %s", err.Error())
51+
os.Exit(1)
52+
}
53+
},
54+
}
55+
56+
func init() {
57+
rootCmd.AddCommand(cleanupCmd)
58+
}

cmd/systemd.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,7 @@ import (
2424
// systemdCmd represents the systemd command
2525
var systemdCmd = &cobra.Command{
2626
Use: "systemd",
27-
Short: "A brief description of your command",
28-
Long: `A longer description that spans multiple lines and likely contains examples
29-
and usage of using your command. For example:
30-
31-
Cobra is a CLI library for Go that empowers applications.
32-
This application is a tool to generate the needed files
33-
to quickly create a Cobra application.`,
27+
Short: "systemd generate a systemd service, that you can use for the tagit service",
3428
Run: func(cmd *cobra.Command, args []string) {
3529
fields := &systemd.Fields{
3630
User: cmd.PersistentFlags().Lookup("user").Value.String(),

pkg/tagit/tagit.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,21 @@ func (t *TagIt) Run(ctx context.Context) {
9797
}
9898
}
9999

100+
// CleanupTags removes all tags with the given prefix from the service.
101+
func (t *TagIt) CleanupTags() error {
102+
service, err := t.getService()
103+
if err != nil {
104+
return fmt.Errorf("error getting service: %w", err)
105+
}
106+
registration := t.copyServiceToRegistration(service)
107+
updatedTags, tagged := t.excludeTagged(registration.Tags)
108+
if tagged {
109+
registration.Tags = updatedTags
110+
return t.client.Agent().ServiceRegister(registration)
111+
}
112+
return nil
113+
}
114+
100115
// runScript runs a command and returns the output.
101116
func (t *TagIt) runScript() ([]byte, error) {
102117
log.WithFields(log.Fields{

pkg/tagit/tagit_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package tagit
22

33
import (
4+
"context"
45
"fmt"
56
"reflect"
67
"slices"
78
"strings"
9+
"sync/atomic"
810
"testing"
911
"time"
1012

@@ -623,3 +625,150 @@ func TestUpdateServiceTags(t *testing.T) {
623625
})
624626
}
625627
}
628+
629+
func TestCleanupTags(t *testing.T) {
630+
tests := []struct {
631+
name string
632+
serviceID string
633+
mockServices map[string]*api.AgentService
634+
tagPrefix string
635+
mockRegisterErr error
636+
expectError bool
637+
expectTags []string
638+
}{
639+
{
640+
name: "Successful Tag Cleanup",
641+
serviceID: "test-service",
642+
mockServices: map[string]*api.AgentService{
643+
"test-service": {
644+
ID: "test-service",
645+
Tags: []string{"tag-prefix1", "tag-prefix2", "other-tag"},
646+
},
647+
},
648+
tagPrefix: "tag",
649+
expectError: false,
650+
expectTags: []string{"other-tag"},
651+
},
652+
{
653+
name: "No Tag Cleanup needed",
654+
serviceID: "test-service",
655+
mockServices: map[string]*api.AgentService{
656+
"test-service": {
657+
ID: "test-service",
658+
Tags: []string{"prefix1", "prefix2", "other-tag"},
659+
},
660+
},
661+
tagPrefix: "tag",
662+
expectError: false,
663+
expectTags: []string{"prefix1", "prefix2", "other-tag"},
664+
},
665+
{
666+
name: "Service Not Found",
667+
serviceID: "non-existent-service",
668+
mockServices: map[string]*api.AgentService{
669+
"other-service": {
670+
ID: "other-service",
671+
Tags: []string{"some-tag", "another-tag"},
672+
},
673+
},
674+
tagPrefix: "tag-prefix",
675+
expectError: true,
676+
},
677+
{
678+
name: "Consul Register Error",
679+
serviceID: "test-service",
680+
mockServices: map[string]*api.AgentService{
681+
"test-service": {
682+
ID: "test-service",
683+
Tags: []string{"tag-prefix1", "other-tag"},
684+
},
685+
},
686+
tagPrefix: "tag",
687+
mockRegisterErr: fmt.Errorf("consul register error"),
688+
expectError: true,
689+
},
690+
}
691+
692+
for _, tt := range tests {
693+
t.Run(tt.name, func(t *testing.T) {
694+
mockConsulClient := &MockConsulClient{
695+
MockAgent: &MockAgent{
696+
ServicesFunc: func() (map[string]*api.AgentService, error) {
697+
return tt.mockServices, nil
698+
},
699+
ServiceRegisterFunc: func(reg *api.AgentServiceRegistration) error {
700+
// Ensure the service exists in the mock data
701+
if service, exists := tt.mockServices[reg.ID]; exists && tt.mockRegisterErr == nil {
702+
// Update the tags of the service
703+
service.Tags = reg.Tags
704+
tt.mockServices[reg.ID] = service // Update the map with the modified service
705+
}
706+
return tt.mockRegisterErr
707+
},
708+
},
709+
}
710+
tagit := New(mockConsulClient, nil, tt.serviceID, "", 0, tt.tagPrefix)
711+
712+
err := tagit.CleanupTags()
713+
if (err != nil) != tt.expectError {
714+
t.Errorf("CleanupTags() error = %v, wantErr %v", err, tt.expectError)
715+
}
716+
717+
if !tt.expectError {
718+
updatedService := tt.mockServices[tt.serviceID]
719+
if updatedService != nil && !reflect.DeepEqual(updatedService.Tags, tt.expectTags) {
720+
t.Errorf("Expected tags after cleanup: %v, got: %v", tt.expectTags, updatedService.Tags)
721+
}
722+
}
723+
})
724+
}
725+
}
726+
727+
func TestRun(t *testing.T) {
728+
// Setup
729+
ctx, cancel := context.WithCancel(context.Background())
730+
defer cancel()
731+
732+
updateServiceTagsCalled := atomic.Int32{}
733+
mockExecutor := &MockCommandExecutor{
734+
MockOutput: []byte("new-tag1 new-tag2"),
735+
MockError: nil,
736+
}
737+
mockConsulClient := &MockConsulClient{
738+
MockAgent: &MockAgent{
739+
ServicesFunc: func() (map[string]*api.AgentService, error) {
740+
updateServiceTagsCalled.Add(1)
741+
if updateServiceTagsCalled.Load() == 2 {
742+
return nil, fmt.Errorf("enter error")
743+
}
744+
return map[string]*api.AgentService{
745+
"test-service": {
746+
ID: "test-service",
747+
Tags: []string{"old-tag"},
748+
},
749+
}, nil
750+
},
751+
ServiceRegisterFunc: func(reg *api.AgentServiceRegistration) error {
752+
753+
return nil
754+
},
755+
},
756+
}
757+
758+
tagit := New(mockConsulClient, mockExecutor, "test-service", "echo test", 100*time.Millisecond, "tag")
759+
760+
// Start Run in a goroutine
761+
go tagit.Run(ctx)
762+
763+
// Allow some time to pass and then cancel the context
764+
time.Sleep(350 * time.Millisecond) // Adjust this duration as needed
765+
cancel()
766+
767+
// Allow some time for the goroutine to react to the context cancellation
768+
time.Sleep(50 * time.Millisecond)
769+
770+
// Check if updateServiceTags was called as expected
771+
if updateServiceTagsCalled.Load() < 2 || updateServiceTagsCalled.Load() > 3 {
772+
t.Errorf("Expected updateServiceTags to be called 2 or 3 times, got %d", updateServiceTagsCalled)
773+
}
774+
}

0 commit comments

Comments
 (0)