github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/ddlresolver/registry_resolver.go (about) 1 // Copyright (c) 2021-2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package ddlresolver 6 7 import ( 8 "context" 9 "encoding/json" 10 "fmt" 11 "os" 12 "path/filepath" 13 "strings" 14 15 rc "github.com/choria-io/go-choria/client/choria_registryclient" 16 "github.com/choria-io/go-choria/inter" 17 iu "github.com/choria-io/go-choria/internal/util" 18 ) 19 20 // RegistryDDLResolver resolves DDL via the Choria Registry 21 type RegistryDDLResolver struct{} 22 23 func (r *RegistryDDLResolver) String() string { 24 return "Choria Registry DDL Resolver" 25 } 26 27 func (r *RegistryDDLResolver) DDL(ctx context.Context, kind string, name string, target any, fw inter.Framework) error { 28 b, err := r.DDLBytes(ctx, kind, name, fw) 29 if err != nil { 30 return err 31 } 32 33 return json.Unmarshal(b, target) 34 } 35 36 func (r *RegistryDDLResolver) findInCache(kind string, name string, fw inter.Framework) ([]byte, error) { 37 cache := fw.Configuration().Choria.RegistryClientCache 38 if !iu.FileIsDir(cache) { 39 return nil, fmt.Errorf("no cache found") 40 } 41 42 cfile := filepath.Join(cache, kind, name+".json") 43 if !iu.FileExist(cfile) { 44 return nil, fmt.Errorf("not found in cache") 45 } 46 47 return os.ReadFile(cfile) 48 } 49 50 func (r *RegistryDDLResolver) storeInCache(kind string, name string, data []byte, fw inter.Framework) error { 51 cache := fw.Configuration().Choria.RegistryClientCache 52 targetDir := filepath.Join(cache, kind) 53 if !iu.FileIsDir(targetDir) { 54 err := os.MkdirAll(targetDir, 0700) 55 if err != nil { 56 return err 57 } 58 } 59 60 return os.WriteFile(filepath.Join(targetDir, name+".json"), data, 0644) 61 } 62 63 func (r *RegistryDDLResolver) DDLBytes(ctx context.Context, kind string, name string, fw inter.Framework) ([]byte, error) { 64 if kind != "agent" { 65 return nil, fmt.Errorf("unsupported ddl type %q", kind) 66 } 67 68 if fw.Configuration().Choria.RegistryClientCache == "" { 69 return nil, fmt.Errorf("registry client is not enabled") 70 } 71 72 if fw.Configuration().InitiatedByServer { 73 return nil, fmt.Errorf("servers cannot resolve DDLs via the registry") 74 } 75 76 cached, _ := r.findInCache(kind, name, fw) 77 if cached != nil { 78 return cached, nil 79 } 80 81 if fw.Configuration().RegistryCacheOnly { 82 return nil, fmt.Errorf("registry client is operating in cache only mode") 83 } 84 85 client, err := rc.New(fw) 86 if err != nil { 87 return nil, err 88 } 89 90 res, err := client.Ddl(name, kind).Format("json").Do(ctx) 91 if err != nil { 92 return nil, err 93 } 94 95 if res.Stats().ResponsesCount() < 1 { 96 return nil, fmt.Errorf("did not receive any response from the registry") 97 } 98 99 ddl := []byte{} 100 logger := fw.Logger("registry") 101 102 res.EachOutput(func(res *rc.DdlOutput) { 103 logger.Infof("Resolved DDL via service host %s", res.ResultDetails().Sender()) 104 105 if !res.ResultDetails().OK() { 106 err = fmt.Errorf("invalid response: %s", res.ResultDetails().StatusMessage()) 107 return 108 } 109 110 ddl = []byte(res.Ddl()) 111 }) 112 if err != nil { 113 return nil, err 114 } 115 116 err = r.storeInCache(kind, name, ddl, fw) 117 if err != nil { 118 logger.Warnf("Could not save DDL for %s/%s in local cache: %s", kind, name, err) 119 } 120 121 return ddl, nil 122 } 123 124 func (r *RegistryDDLResolver) cacheDDLNames(kind string, fw inter.Framework) ([]string, error) { 125 entries, err := os.ReadDir(filepath.Join(fw.Configuration().Choria.RegistryClientCache, kind)) 126 if err != nil { 127 return nil, err 128 } 129 130 found := []string{} 131 132 for _, entry := range entries { 133 if entry.IsDir() { 134 continue 135 } 136 if !strings.HasSuffix(entry.Name(), ".json") { 137 continue 138 } 139 140 found = append(found, strings.TrimSuffix(entry.Name(), ".json")) 141 } 142 143 return found, nil 144 } 145 146 func (r *RegistryDDLResolver) DDLNames(ctx context.Context, kind string, fw inter.Framework) ([]string, error) { 147 if kind != "agent" { 148 return nil, fmt.Errorf("unsupported ddl type %q", kind) 149 } 150 151 if fw.Configuration().Choria.RegistryClientCache == "" { 152 return nil, fmt.Errorf("registry client is not enabled") 153 } 154 155 if fw.Configuration().InitiatedByServer { 156 return nil, fmt.Errorf("servers cannot resolve DDLs via the registry") 157 } 158 159 if fw.Configuration().RegistryCacheOnly { 160 return r.cacheDDLNames(kind, fw) 161 } 162 163 client, err := rc.New(fw) 164 if err != nil { 165 return nil, err 166 } 167 168 res, err := client.Names(kind).Do(ctx) 169 if err != nil { 170 return nil, err 171 } 172 173 names := []string{} 174 if res.Stats().ResponsesCount() < 1 { 175 return nil, fmt.Errorf("did not receive any response from the registry") 176 } 177 178 res.EachOutput(func(res *rc.NamesOutput) { 179 fw.Logger("registry").Infof("Resolved DDL via service host %s", res.ResultDetails().Sender()) 180 181 if !res.ResultDetails().OK() { 182 err = fmt.Errorf("invalid response: %s", res.ResultDetails().StatusMessage()) 183 return 184 } 185 186 for _, v := range res.Names() { 187 name, ok := v.(string) 188 if ok { 189 names = append(names, name) 190 } 191 } 192 }) 193 194 return names, err 195 }