Skip to content

Commit

Permalink
Add reminder service with empty sendReminders logic
Browse files Browse the repository at this point in the history
Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com>
  • Loading branch information
Vyom-Yadav committed Mar 13, 2024
1 parent 935880f commit 435be78
Show file tree
Hide file tree
Showing 11 changed files with 1,092 additions and 0 deletions.
105 changes: 105 additions & 0 deletions cmd/reminder/app/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//
// Copyright 2024 Stacklok, Inc.
//
// 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 app provides the cli subcommands for managing the reminder service
package app

import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/stacklok/minder/internal/config"
reminderconfig "github.com/stacklok/minder/internal/config/reminder"
"github.com/stacklok/minder/internal/util/cli"
)

var (
// RootCmd represents the base command when called without any subcommands
RootCmd = &cobra.Command{
Use: "reminder",
Short: "reminder controls the reminder service",
Long: `reminder sends entity reconciliation requests to the minder server`,
}
)

const configFileName = "reminder-config.yaml"

// Execute adds all child commands to the root command and sets flags appropriately.
func Execute() {
RootCmd.SetOut(os.Stdout)
RootCmd.SetErr(os.Stderr)
err := RootCmd.ExecuteContext(context.Background())
cli.ExitNicelyOnError(err, "Error executing root command")
}

func init() {
cobra.OnInitialize(initConfig)
reminderconfig.SetViperDefaults(viper.GetViper())

if err := reminderconfig.RegisterReminderFlags(viper.GetViper(), RootCmd.PersistentFlags()); err != nil {
RootCmd.PrintErrln("Error registering reminder flags")
os.Exit(1)
}

RootCmd.PersistentFlags().String("config", "", fmt.Sprintf("config file (default is $PWD/%s)", configFileName))
if err := viper.BindPFlag("config", RootCmd.PersistentFlags().Lookup("config")); err != nil {
RootCmd.Printf("error: %s", err)
os.Exit(1)
}
}

func initConfig() {
cfgFile := viper.GetString("config")
cfgFileData, err := config.GetConfigFileData(cfgFile, filepath.Join(".", configFileName))
if err != nil {
RootCmd.PrintErrln(err)
os.Exit(1)
}

keysWithNullValue := config.GetKeysWithNullValueFromYAML(cfgFileData, "")
if len(keysWithNullValue) > 0 {
RootCmd.PrintErrln("Error: The following configuration keys are missing values:")
for _, key := range keysWithNullValue {
RootCmd.PrintErrln("Null Value at: " + key)
}
os.Exit(1)
}

err = reminderconfig.ValidateConfig(cfgFileData)
if err != nil {
RootCmd.PrintErrln(err)
os.Exit(1)
}

if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
// use defaults
viper.SetConfigName(strings.TrimSuffix(configFileName, filepath.Ext(configFileName)))
viper.AddConfigPath(".")
}
viper.SetConfigType("yaml")
viper.AutomaticEnv()

if err = viper.ReadInConfig(); err != nil {
fmt.Println("Error reading config file:", err)
}
}
85 changes: 85 additions & 0 deletions cmd/reminder/app/start.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Copyright 2024 Stacklok, Inc.
//
// 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 app

import (
"database/sql"
"fmt"
"os"
"os/signal"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"

"github.com/stacklok/minder/internal/config"
reminderconfig "github.com/stacklok/minder/internal/config/reminder"
"github.com/stacklok/minder/internal/db"
"github.com/stacklok/minder/internal/reminder"
)

var startCmd = &cobra.Command{
Use: "start",
Short: "Start the reminder process",
Long: `Start the reminder process to send reminders to the minder server to process entities in background.`,
RunE: start,
}

func start(cmd *cobra.Command, _ []string) error {
ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt)
defer cancel()

cfg, err := config.ReadConfigFromViper[reminderconfig.Config](viper.GetViper())
if err != nil {
return fmt.Errorf("unable to read config: %w", err)
}

err = reminderconfig.ValidateConfig(cfg)
if err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}

dbConn, _, err := cfg.Database.GetDBConnection(ctx)
if err != nil {
return fmt.Errorf("unable to connect to database: %w", err)
}
defer func(dbConn *sql.DB) {
err := dbConn.Close()
if err != nil {
log.Printf("error closing database connection: %v", err)
}
}(dbConn)

store := db.NewStore(dbConn)
reminderService, err := reminder.NewReminder(store, cfg)
if err != nil {
return fmt.Errorf("unable to create reminder service: %w", err)
}
defer reminderService.Stop()

errg, ctx := errgroup.WithContext(ctx)

errg.Go(func() error {
return reminderService.Start(ctx)
})

return errg.Wait()
}

func init() {
RootCmd.AddCommand(startCmd)
}
22 changes: 22 additions & 0 deletions cmd/reminder/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2024 Stacklok, Inc.
//
// 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 main provides the entrypoint for the reminder service
package main

import "github.com/stacklok/minder/cmd/reminder/app"

func main() {
app.Execute()
}
42 changes: 42 additions & 0 deletions config/reminder-config.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#
# Copyright 2024 Stacklok, Inc.
#
# 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.

recurrence:
interval: "1h"
batch_size: 100
max_per_project: 10
min_project_fetch_limit: 10
min_elapsed: "1h"

database:
dbhost: "postgres"
dbport: 5432
dbuser: postgres
dbpass: postgres
dbname: minder
sslmode: disable

cursor_file: "/tmp/reminder-cursor"

logging_level: "debug"

events:
sql_connection:
dbhost: "reminder-event-postgres"
dbport: 5432
dbuser: postgres
dbpass: postgres
dbname: reminder
sslmode: disable
52 changes: 52 additions & 0 deletions docker/reminder/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#
# Copyright 2024 Stacklok, Inc.
#
# 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.

FROM golang:1.22.1@sha256:34ce21a9696a017249614876638ea37ceca13cdd88f582caad06f87a8aa45bf3 AS builder
ENV APP_ROOT=/opt/app-root
ENV GOPATH=$APP_ROOT

WORKDIR $APP_ROOT/src/
ADD go.mod go.sum $APP_ROOT/src/
RUN go mod download

# Add source code
ADD ./ $APP_ROOT/src/

RUN CGO_ENABLED=0 go build -trimpath -o reminder ./cmd/reminder

# Create a "nobody" non-root user for the next image by crafting an /etc/passwd
# file that the next image can copy in. This is necessary since the next image
# is based on scratch, which doesn't have adduser, cat, echo, or even sh.
RUN echo "nobody:x:65534:65534:Nobody:/:" > /etc_passwd

RUN mkdir -p /app

FROM scratch

COPY --chown=65534:65534 --from=builder /app /app

WORKDIR /app

COPY --from=builder /opt/app-root/src/reminder /usr/bin/reminder

# Copy the /etc_passwd file we created in the builder stage into /etc/passwd in
# the target stage. This creates a new non-root user as a security best
# practice.
COPY --from=builder /etc_passwd /etc/passwd

USER nobody

# Set the binary as the entrypoint of the container
ENTRYPOINT ["/usr/bin/reminder"]
Loading

0 comments on commit 435be78

Please sign in to comment.