diff --git a/cmd/root.go b/cmd/root.go index 3b94152..95b50c5 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -41,7 +41,15 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - runCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tagit.yaml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tagit.yaml)") + rootCmd.PersistentFlags().StringP("consul-addr", "c", "127.0.0.1:8500", "consul address") + rootCmd.PersistentFlags().StringP("service-id", "s", "", "consul service id") + rootCmd.MarkPersistentFlagRequired("service-id") + rootCmd.PersistentFlags().StringP("script", "x", "", "path to script used to generate tags") + rootCmd.MarkPersistentFlagRequired("script") + rootCmd.PersistentFlags().StringP("tag-prefix", "p", "tagged", "prefix to be added to tags") + rootCmd.PersistentFlags().StringP("interval", "i", "60s", "interval to run the script") + rootCmd.PersistentFlags().StringP("token", "t", "", "consul token") } // initConfig reads in config file and ENV variables if set. diff --git a/cmd/run.go b/cmd/run.go index d4bad83..f569314 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -36,22 +36,8 @@ var runCmd = &cobra.Command{ example: tagit run -s my-super-service -x '/tmp/tag-role.sh' `, Run: func(cmd *cobra.Command, args []string) { - serviceID := cmd.PersistentFlags().Lookup("service-id").Value.String() - script := cmd.PersistentFlags().Lookup("script").Value.String() - tagPrefix := cmd.PersistentFlags().Lookup("tag-prefix").Value.String() - interval := cmd.PersistentFlags().Lookup("interval").Value.String() + interval := cmd.InheritedFlags().Lookup("interval").Value.String() ctx := context.Background() - - if serviceID == "" { - fmt.Println("service-id is required") - os.Exit(1) - } - - if script == "" { - fmt.Println("script is required") - os.Exit(1) - } - if interval == "" || interval == "0" { fmt.Println("interval is required") os.Exit(1) @@ -64,26 +50,26 @@ example: tagit run -s my-super-service -x '/tmp/tag-role.sh' } config := api.DefaultConfig() - config.Address = cmd.PersistentFlags().Lookup("consul-addr").Value.String() - config.Token = cmd.PersistentFlags().Lookup("token").Value.String() + config.Address = cmd.InheritedFlags().Lookup("consul-addr").Value.String() + config.Token = cmd.InheritedFlags().Lookup("token").Value.String() consulClient, err := api.NewClient(config) if err != nil { fmt.Printf("error creating consul client: %s", err.Error()) os.Exit(1) } - t := tagit.New(tagit.NewConsulAPIWrapper(consulClient), &tagit.CmdExecutor{}, serviceID, script, validInterval, tagPrefix) + t := tagit.New( + tagit.NewConsulAPIWrapper(consulClient), + &tagit.CmdExecutor{}, + cmd.InheritedFlags().Lookup("service-id").Value.String(), + cmd.InheritedFlags().Lookup("script").Value.String(), + validInterval, + cmd.InheritedFlags().Lookup("tag-prefix").Value.String(), + ) t.Run(ctx) }, } func init() { rootCmd.AddCommand(runCmd) - - runCmd.PersistentFlags().StringP("consul-addr", "c", "127.0.0.1:8500", "consul address") - runCmd.PersistentFlags().StringP("service-id", "s", "", "consul service id") - runCmd.PersistentFlags().StringP("script", "x", "", "path to script used to generate tags") - runCmd.PersistentFlags().StringP("tag-prefix", "p", "tagged", "prefix to be added to tags") - runCmd.PersistentFlags().StringP("interval", "i", "60s", "interval to run the script") - runCmd.PersistentFlags().StringP("token", "t", "", "consul token") } diff --git a/cmd/systemd.go b/cmd/systemd.go new file mode 100644 index 0000000..9019e2b --- /dev/null +++ b/cmd/systemd.go @@ -0,0 +1,53 @@ +/* +Copyright © 2024 Juliano Martinez + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "fmt" + "github.com/ncode/tagit/pkg/systemd" + "github.com/spf13/cobra" +) + +// systemdCmd represents the systemd command +var systemdCmd = &cobra.Command{ + Use: "systemd", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + fields := &systemd.Fields{ + User: cmd.PersistentFlags().Lookup("user").Value.String(), + Group: cmd.PersistentFlags().Lookup("group").Value.String(), + ConsulAddr: cmd.InheritedFlags().Lookup("consul-addr").Value.String(), + ServiceID: cmd.InheritedFlags().Lookup("service-id").Value.String(), + Script: cmd.InheritedFlags().Lookup("script").Value.String(), + TagPrefix: cmd.InheritedFlags().Lookup("tag-prefix").Value.String(), + Interval: cmd.InheritedFlags().Lookup("interval").Value.String(), + Token: cmd.InheritedFlags().Lookup("token").Value.String(), + } + fmt.Println(systemd.RenderTemplate(fields)) + }, +} + +func init() { + rootCmd.AddCommand(systemdCmd) + systemdCmd.PersistentFlags().StringP("user", "u", "nobody", "user to run the service") + systemdCmd.PersistentFlags().StringP("group", "g", "nobody", "group to run the service") +} diff --git a/pkg/systemd/systemd.go b/pkg/systemd/systemd.go new file mode 100644 index 0000000..de08249 --- /dev/null +++ b/pkg/systemd/systemd.go @@ -0,0 +1,54 @@ +package systemd + +import ( + "bytes" + "text/template" +) + +// Fields is the struct that holds the fields for the systemd service. +type Fields struct { + ServiceID string + Script string + TagPrefix string + Interval string + Token string + ConsulAddr string + User string + Group string +} + +// serviceTemplate is the template for the systemd service. +var serviceTemplate = ` +[Unit] +Description=Tagit {{ .ServiceID }} +After=network.target +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +ExecStart=/usr/bin/tagit run -s {{ .ServiceID }} -x {{ .Script }} -p {{ .TagPrefix }} -i {{ .Interval }}{{- if .Token }} -t {{ .Token }}{{- end }}{{ if .ConsulAddr }} -c {{ .ConsulAddr }}{{- end }} +Environment=HOME=/var/run/taggit/{{ .ServiceID }} +Restart=always +User={{ .User }} +Group={{ .Group }} + +[Install] +WantedBy=multi-user.target +` + +// RenderTemplate renders the template for the systemd service. +func RenderTemplate(fields *Fields) (string, error) { + tmpl, err := template.New("serviceTemplate").Parse(serviceTemplate) + if err != nil { + return "", err + } + + var tmplBuffer bytes.Buffer + err = tmpl.Execute(&tmplBuffer, fields) + if err != nil { + return "", err + } + + return tmplBuffer.String(), nil +} diff --git a/pkg/systemd/systemd_test.go b/pkg/systemd/systemd_test.go new file mode 100644 index 0000000..c69b346 --- /dev/null +++ b/pkg/systemd/systemd_test.go @@ -0,0 +1,116 @@ +package systemd + +import ( + "strings" + "testing" +) + +func TestRenderTemplate(t *testing.T) { + // Define a test case struct + type testCase struct { + name string + fields Fields + wantErr bool + checkStr string // A substring we expect in the output + expectStr bool // A flag to indicate the absence of a substring + } + + // Define your test cases + testCases := []testCase{ + { + name: "Basic test", + fields: Fields{ + ServiceID: "testservice", + Script: "testscript", + TagPrefix: "testprefix", + Interval: "testinterval", + Token: "testtoken", + ConsulAddr: "testaddr", + User: "testuser", + Group: "testgroup", + }, + wantErr: false, + checkStr: "ExecStart=/usr/bin/tagit run -s testservice -x testscript", + expectStr: true, + }, + { + name: "With Token", + fields: Fields{ + ServiceID: "testservice", + Token: "sometoken", + }, + wantErr: false, + checkStr: "-t sometoken", + expectStr: true, + }, + { + name: "Without Token", + fields: Fields{ + ServiceID: "testservice", + }, + wantErr: false, + checkStr: "-t", + expectStr: false, + }, + { + name: "With Consul Address", + fields: Fields{ + ServiceID: "testservice", + ConsulAddr: "someaddress", + }, + wantErr: false, + checkStr: "-c someaddress", + expectStr: true, + }, + { + name: "Without Consul Address", + fields: Fields{ + ServiceID: "testservice", + }, + wantErr: false, + checkStr: "-c someaddress", + expectStr: false, + }, + { + name: "Empty Fields", + fields: Fields{}, + wantErr: false, + checkStr: "ExecStart=/usr/bin/tagit run -s", + expectStr: true, + }, + { + name: "Consul Address Only", + fields: Fields{ + ConsulAddr: "127.0.0.1", + }, + wantErr: false, + checkStr: "-c 127.0.0.1", + expectStr: true, + }, + } + + // Iterate over the test cases + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, err := RenderTemplate(&tc.fields) + if (err != nil) != tc.wantErr { + t.Errorf("RenderTemplate() error = %v, wantErr %v", err, tc.wantErr) + return + } + + // Check for the presence or absence of the token + if tc.expectStr && !strings.Contains(got, tc.checkStr) { + t.Errorf("RenderTemplate() = %v, want %v", got, tc.checkStr) + } else if !tc.expectStr && strings.Contains(got, tc.checkStr) { + t.Errorf("RenderTemplate() = %v, should not contain %v", got, tc.checkStr) + } + }) + } +} + +func TestRenderTemplateFailure(t *testing.T) { + _, err := RenderTemplate(nil) + if err == nil { + t.Errorf("RenderTemplate() with nil input did not fail as expected") + } +}