Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ CURRENT_DIR=$(shell pwd)
DIST_DIR=${CURRENT_DIR}/build/dist
CLI_NAME=microcks
BIN_NAME=microcks
WATCHER_NAME=watcher

HOST_OS=$(shell go env GOOS)
HOST_ARCH=$(shell go env GOARCH)
Expand All @@ -23,3 +24,7 @@ build-binaries:
make BIN_NAME=${CLI_NAME}-darwin-arm64 GOOS=darwin GOARCH=arm64 build-local
make BIN_NAME=${CLI_NAME}-windows-amd64.exe GOOS=windows build-local
make BIN_NAME=${CLI_NAME}-windows-386.exe GOOS=windows GOARCH=386 build-local

.PHONY: build-watcher
build-watcher:
go build -o ${DIST_DIR}/${BIN_NAME}-${WATCHER_NAME} ${PACKAGE}/pkg/importer
2 changes: 1 addition & 1 deletion cmd/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ microcks context httP://localhost:8080 --delete`,
},
}

ctxCmd.Flags().BoolVar(&delete, "delete", false, "Delete a context")
ctxCmd.Flags().BoolVarP(&delete, "delete", "d", false, "Delete a context")

return ctxCmd
}
Expand Down
28 changes: 28 additions & 0 deletions cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ import (

"github.com/microcks/microcks-cli/pkg/config"
"github.com/microcks/microcks-cli/pkg/connectors"
"github.com/microcks/microcks-cli/pkg/errors"
"github.com/spf13/cobra"
)

func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command {
var watch bool

var importCmd = &cobra.Command{
Use: "import",
Short: "import API artifacts on Microcks server",
Expand All @@ -35,6 +38,7 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command
// Parse subcommand args first.
if len(args) == 0 {
fmt.Println("import command require <specificationFile1[:primary],specificationFile2[:primary]> args")
cmd.HelpFunc()(cmd, args)
os.Exit(1)
}

Expand All @@ -55,6 +59,10 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command
return
}

if globalClientOpts.Context == "" {
globalClientOpts.Context = localConfig.CurrentContext
}

mc, err := connectors.NewClient(*globalClientOpts)
if err != nil {
fmt.Printf("error %v", err)
Expand Down Expand Up @@ -82,10 +90,30 @@ func NewImportCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command
os.Exit(1)
}
fmt.Printf("Microcks has discovered '%s'\n", msg)

if watch {
watchFile, err := config.DefaultLocalWatchPath()
errors.CheckError(err)
watchCfg, err := config.ReadLocalWatchConfig(watchFile)
errors.CheckError(err)
if watchCfg == nil {
watchCfg = &config.WatchConfig{}
}

watchCfg.UpsertEntry(config.WatchEntry{
FilePath: f,
Context: []string{globalClientOpts.Context},
MainArtifact: mainArtifact,
})
//write watch file
err = config.WriteLocalWatchConfig(*watchCfg, watchFile)
errors.CheckError(err)
}
}

},
}

importCmd.Flags().BoolVar(&watch, "watch", false, "Keep watch on file changes and re-import it on change ")
return importCmd
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/coreos/go-oidc/v3 v3.14.1
github.com/docker/docker v28.0.4+incompatible
github.com/docker/go-connections v0.5.0
github.com/fsnotify/fsnotify v1.9.0
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cobra v1.9.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down
61 changes: 61 additions & 0 deletions pkg/config/localconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
"slices"

configUtil "github.com/microcks/microcks-cli/pkg/util"
)
Expand Down Expand Up @@ -60,6 +61,16 @@ type Auth struct {
ClientSecret string
}

type WatchConfig struct {
Entries []WatchEntry `yaml:"entries"`
}

type WatchEntry struct {
FilePath string `yaml:"filePath"`
Context []string `yaml:"context"`
MainArtifact bool `yaml:"mainartifact"`
}

// ReadLocalConfig loads up the local configuration file. Returns nil if config does not exist
func ReadLocalConfig(path string) (*LocalConfig, error) {
var err error
Expand Down Expand Up @@ -123,6 +134,14 @@ func DefaultLocalConfigPath() (string, error) {
return path.Join(dir, "config"), nil
}

func DefaultLocalWatchPath() (string, error) {
dir, err := DefaultConfigDir()
if err != nil {
return "", err
}
return path.Join(dir, "watch"), nil
}

func ValidateLocalConfig(config LocalConfig) error {
if config.CurrentContext == "" {
return nil
Expand Down Expand Up @@ -331,3 +350,45 @@ func (l *LocalConfig) RemoveAuth(server string) bool {
}
return false
}

func (w *WatchConfig) UpsertEntry(entry WatchEntry) {
for i, e := range w.Entries {
if e.FilePath == entry.FilePath {
contexts := w.Entries[i].Context
if !slices.Contains(contexts, entry.Context[0]) {
entry.Context = append(entry.Context, contexts...)
}
w.Entries[i] = entry
return
}
}
w.Entries = append(w.Entries, entry)
}

func ReadLocalWatchConfig(path string) (*WatchConfig, error) {
var err error
var config WatchConfig

// check file permission only when microcks config exists
if fi, err := os.Stat(path); err == nil {
err = getFilePermission(fi)
if err != nil {
return nil, err
}
}

err = configUtil.UnmarshalLocalFile(path, &config)
if os.IsNotExist(err) {
return nil, nil
}

return &config, nil
}

func WriteLocalWatchConfig(config WatchConfig, cfgPath string) error {
err := os.MkdirAll(path.Dir(cfgPath), os.ModePerm)
if err != nil {
return err
}
return configUtil.MarshalLocalYAMLFile(cfgPath, &config)
}
20 changes: 20 additions & 0 deletions pkg/importer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"fmt"

"github.com/microcks/microcks-cli/pkg/config"
"github.com/microcks/microcks-cli/pkg/errors"
"github.com/microcks/microcks-cli/pkg/importer/watcher"
)

func main() {
watchFile, err := config.DefaultLocalWatchPath()
errors.CheckError(err)

wm, err := watcher.NewWatchManger(watchFile)
errors.CheckError(err)

fmt.Println("[INFO] microcks-watcher started...")
wm.Run()
}
51 changes: 51 additions & 0 deletions pkg/importer/watcher/executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package watcher

import (
"fmt"
"strconv"

"github.com/microcks/microcks-cli/cmd"
"github.com/microcks/microcks-cli/pkg/config"
"github.com/microcks/microcks-cli/pkg/connectors"
)

func TriggerImport(entry config.WatchEntry) {
mainArtifact := strconv.FormatBool(entry.MainArtifact)

args := []string{
entry.FilePath + ":" + mainArtifact,
}

cfgPath, err := config.DefaultLocalConfigPath()
if err != nil {
fmt.Errorf("Error while loading config: %s", err.Error())
}

for _, context := range entry.Context {
importCommand := cmd.NewImportCommand(&connectors.ClientOptions{
ConfigPath: cfgPath,
Context: context,
})
importCommand.SetArgs(args)
err = importCommand.Execute()
if err != nil {
fmt.Printf("Error re-importing %s: %v\n", entry.FilePath, err)
}

fmt.Printf("Imported '%s' in context '%s'\n", entry.FilePath, context)
}
}

func LoadRegistry(watchFilePath string) (*config.WatchConfig, error) {
var watchCfg *config.WatchConfig
watchCfg, err := config.ReadLocalWatchConfig(watchFilePath)
if err != nil {
return nil, err
}

if watchCfg == nil {
watchCfg = &config.WatchConfig{}
}

return watchCfg, nil
}
104 changes: 104 additions & 0 deletions pkg/importer/watcher/watchManager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package watcher

import (
"fmt"
"log"
"sync"

"github.com/fsnotify/fsnotify"
"github.com/microcks/microcks-cli/pkg/config"
"github.com/microcks/microcks-cli/pkg/errors"
)

type WatchManager struct {
fileWatcher *fsnotify.Watcher
configPath string
watchEntries map[string]config.WatchEntry
lock sync.Mutex
}

func NewWatchManger(configPath string) (*WatchManager, error) {
fw, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}

err = fw.Add(configPath)
if err != nil {
return nil, err
}

wm := &WatchManager{
fileWatcher: fw,
configPath: configPath,
watchEntries: make(map[string]config.WatchEntry),
}

err = wm.Reload()
if err != nil {
return nil, err
}

return wm, nil
}

func (wm *WatchManager) Reload() error {
cfg, err := LoadRegistry(wm.configPath)
if err != nil {
return err
}

newFiles := map[string]config.WatchEntry{}
for _, entry := range cfg.Entries {
newFiles[entry.FilePath] = entry
}

// Remove stale watchers
for file := range wm.watchEntries {
if _, exists := newFiles[file]; !exists {
wm.fileWatcher.Remove(file)
}
}

// Add new watchers
for file := range newFiles {
if _, exists := wm.watchEntries[file]; !exists {
err := wm.fileWatcher.Add(file)
if err != nil {
log.Printf("[WARN] Cannot watch file %s: %v", file, err)
continue
}
}
}

wm.watchEntries = newFiles
return nil
}

func (wm *WatchManager) Run() {
for {
select {
case event := <-wm.fileWatcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
if event.Name == wm.configPath {
fmt.Println("[INFO] Reloading config...")
wm.lock.Lock()
err := wm.Reload()
wm.lock.Unlock()
if err != nil {
errors.CheckError(err)
}
} else {
wm.lock.Lock()
entry, exists := wm.watchEntries[event.Name]
wm.lock.Unlock()
if exists {
go TriggerImport(entry)
}
}
}
case err := <-wm.fileWatcher.Errors:
log.Printf("[ERROR] Watcher error: %v", err)
}
}
}