github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/chunks/validate.go (about) 1 // Copyright 2021 The TrueBlocks Authors. All rights reserved. 2 // Use of this source code is governed by a license that can 3 // be found in the LICENSE file. 4 5 package chunksPkg 6 7 import ( 8 "errors" 9 "fmt" 10 "os" 11 "path/filepath" 12 "strings" 13 14 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 15 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config" 16 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/file" 17 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index" 18 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 19 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/pinning" 20 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/validate" 21 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/version" 22 ) 23 24 func (opts *ChunksOptions) validateChunks() error { 25 chain := opts.Globals.Chain 26 27 opts.testLog() 28 29 if opts.BadFlag != nil { 30 return opts.BadFlag 31 } 32 33 if opts.Globals.IsApiMode() { 34 if len(opts.Tag) > 0 { 35 return validate.Usage("The {0} option is not available{1}.", "--tag", " in api mode") 36 } 37 if opts.Truncate != base.NOPOSN { 38 return validate.Usage("The {0} option is not available{1}.", "--truncate", " in api mode") 39 } 40 if opts.Mode == "pins" { 41 return validate.Usage("The {0} mode is not available{1}.", "pins", " in api mode") 42 } 43 } else if len(opts.Tag) > 0 { 44 if !version.IsValidVersion(opts.Tag) { 45 return validate.Usage("The {0} ({1}) must be a valid version string.", "--tag", opts.Tag) 46 } 47 if !config.KnownVersionTag(opts.Tag) { 48 return validate.Usage("The only valid value for {0} is {1}.", "--tag", "trueblocks-core@v2.0.0-release") 49 } 50 } 51 52 if opts.Mode == "pins" { 53 if !opts.List && !opts.Unpin && !opts.Count { 54 return validate.Usage("{0} mode requires {1}.", "pins", "either --list, --count, or --unpin") 55 } 56 57 if opts.Unpin { 58 if !file.FileExists(filepath.Join(".", "unpins")) { 59 return validate.Usage("The file {0} was not found in the local folder.", "./unpins") 60 } 61 62 hasOne := false 63 lines := file.AsciiFileToLines(filepath.Join(".", "unpins")) 64 for _, line := range lines { 65 if pinning.IsValid(line) { 66 hasOne = true 67 break 68 } 69 } 70 if !hasOne { 71 return validate.Usage("The {0} file does not contain any valid CIDs.", "./unpins") 72 } 73 } 74 75 } else { 76 if opts.List { 77 return validate.Usage("The {0} option is only available in {1} mode.", "--list", "pins") 78 } else if opts.Unpin { 79 return validate.Usage("The {0} option is only available in {1} mode.", "--unpin", "pins") 80 } 81 } 82 83 if opts.Count { 84 // TODO: Shouldn't this use validate.Enums? 85 if !strings.Contains("manifest|index|blooms|pins|stats", opts.Mode) { 86 return validate.Usage("The {0} option is only available in {1}.", "--count", "these modes: manifest|index|blooms|pins|stats") 87 } 88 } 89 90 if !config.IsChainConfigured(chain) { 91 return validate.Usage("chain {0} is not properly configured.", chain) 92 } 93 94 err := validate.ValidateEnumRequired("mode", opts.Mode, "[manifest|index|blooms|pins|addresses|appearances|stats]") 95 if err != nil { 96 return err 97 } 98 99 if opts.Diff { 100 if opts.Mode != "index" { 101 return validate.Usage("The {0} option is only available in {1} mode.", "--diff", "index") 102 } 103 path := os.Getenv("TB_CHUNKS_DIFFPATH") 104 if path == "" { 105 return validate.Usage("The {0} option requires {1}.", "--diff", "TB_CHUNKS_DIFFPATH to be set") 106 } 107 if !file.FolderExists(path) { 108 return fmt.Errorf("the path TB_CHUNKS_DIFFPATH=%s does not exist", path) 109 } 110 } 111 112 isIndexOrManifest := opts.Mode == "index" || opts.Mode == "manifest" 113 if !isIndexOrManifest { 114 if err = opts.isDisallowed(!isIndexOrManifest /* i.e., true */, opts.Mode); err != nil { 115 return err 116 } 117 118 if opts.Check { 119 return validate.Usage("The {0} option is not available{1}.", "--check", " in "+opts.Mode+" mode") 120 } 121 } 122 123 isPin := opts.Pin 124 isCheck := opts.Check 125 isPublish := opts.Publish 126 isRemote := opts.Remote 127 isRewrite := opts.Rewrite 128 isDeep := opts.Deep 129 130 if !isIndexOrManifest && (isPin || isPublish || isRemote || isDeep || isCheck) { 131 return validate.Usage("The {0} options is only available in {1} or {2} mode.", "--pin, --publish, --remote, --check, and --deep", "manifest", "index") 132 } 133 134 if opts.Mode == "manifest" && opts.Remote { 135 // this is okay 136 } else if !isCheck && !isPin && (isRemote || isDeep) { 137 return validate.Usage("The {0} options require {1}.", "--remote and --deep", "--pin or --check") 138 } 139 140 if isPin { 141 if isRemote { 142 apiKey, secret, jwt := config.GetKey("pinata").ApiKey, config.GetKey("pinata").Secret, config.GetKey("pinata").Jwt 143 if secret == "" && jwt == "" { 144 return validate.Usage("Either the {0} key or the {1} is required.", "secret", "jwt") 145 } 146 if secret != "" && apiKey == "" { 147 return validate.Usage("If the {0} key is present, so must be the {1}.", "secret", "apiKey") 148 } 149 if config.GetPinning().RemotePinUrl == "" { 150 return validate.Usage("The {0} option requires {1}.", "--pin --remote", "a remotePinUrl") 151 } 152 } else { 153 if !config.IpfsRunning() { 154 return validate.Usage("The {0} option requires {1}.", "--pin", "a locally running IPFS daemon") 155 } 156 if config.GetPinning().LocalPinUrl == "" { 157 return validate.Usage("The {0} option requires {1}.", "--pin", "a localPinUrl") 158 } 159 } 160 } else if isRewrite { 161 return validate.Usage("The {0} option requires {1}.", "--rewrite", "--pin") 162 } 163 164 if opts.Publish { 165 return validate.Usage("The {0} option is currenlty unavailable.", "--publish") 166 } 167 168 if opts.Mode != "index" { 169 if len(opts.Tag) > 0 { 170 return validate.Usage("The {0} option is only available {1}.", "--tag", "in index mode") 171 } 172 if opts.Truncate != base.NOPOSN { 173 return validate.Usage("The {0} option is only available {1}.", "--truncate", "in index mode") 174 } 175 if len(opts.Belongs) > 0 { 176 return validate.Usage("The {0} option requires {1}.", "--belongs", "the index mode") 177 } 178 179 } else { 180 if len(opts.Belongs) > 0 { 181 err := validate.ValidateAtLeastOneAddr(opts.Belongs) 182 if err != nil { 183 return err 184 } 185 if opts.Globals.Verbose { 186 return validate.Usage("Choose either {0} or {1}, not both.", "--verbose", "--belongs") 187 } 188 if len(opts.Blocks) == 0 { 189 return validate.Usage("You must specify at least one {0} with the {1} option", "block identifier", "--belongs") 190 } 191 if opts.Globals.Format != "json" { 192 return validate.Usage("The {0} option only works with {1}", "--belongs", "--fmt json") 193 } 194 } 195 } 196 197 if err = opts.isDisallowed(opts.Globals.IsApiMode(), "API"); err != nil { 198 return err 199 } 200 201 if err = opts.isDisallowed(opts.Globals.TestMode, "test"); err != nil { 202 return err 203 } 204 205 if len(opts.Publisher) > 0 { 206 err := validate.ValidateExactlyOneAddr([]string{opts.Publisher}) 207 if err != nil { 208 return err 209 } 210 } 211 212 err = validate.ValidateIdentifiers( 213 chain, 214 opts.Blocks, 215 validate.ValidBlockIdWithRangeAndDate, 216 1, 217 &opts.BlockIds, 218 ) 219 if err != nil { 220 if invalidLiteral, ok := err.(*validate.InvalidIdentifierLiteralError); ok { 221 return invalidLiteral 222 } 223 if errors.Is(err, validate.ErrTooManyRanges) { 224 return validate.Usage("Specify only a single block range at a time.") 225 } 226 return err 227 } 228 229 if opts.Diff && len(opts.BlockIds) != 1 { 230 return validate.Usage("The {0} option requires exactly one block identifier.", "--diff") 231 } 232 233 if opts.FirstBlock != 0 || opts.LastBlock != base.NOPOSN || opts.MaxAddrs != base.NOPOS { 234 if opts.FirstBlock >= opts.LastBlock { 235 msg := fmt.Sprintf("first_block (%d) must be strictly earlier than last_block (%d).", opts.FirstBlock, opts.LastBlock) 236 return validate.Usage(msg) 237 } 238 if len(opts.Belongs) == 0 && opts.Mode != "addresses" && opts.Mode != "appearances" { 239 return validate.Usage("some options are only available with {0}.", "the addresses, the appearances, or the index --belongs modes") 240 } 241 // TODO: We should check that the first and last blocks are inside the ranges implied by the block ids 242 // if len(opts.BlockIds) > 0 { 243 // } 244 } 245 246 if err := index.IsInitialized(chain, config.ExpectedVersion()); err != nil { 247 isTag := len(opts.Tag) != 0 248 isRemoteMan := opts.Mode == "manifest" && opts.Remote 249 if !isTag && !isRemoteMan { 250 if (errors.Is(err, index.ErrNotInitialized) || errors.Is(err, index.ErrIncorrectHash)) && 251 !opts.Globals.IsApiMode() { 252 logger.Fatal(err) 253 } else { 254 return err 255 } 256 } 257 // It's okay to mismatch versions if we're tagging or downloading a new manifest 258 } 259 260 return opts.Globals.Validate() 261 } 262 263 func (opts *ChunksOptions) isDisallowed(test bool, mode string) error { 264 if test { 265 if opts.Pin { 266 return validate.Usage("The {0} option is not available{1}.", "--pin", " in "+mode+" mode") 267 } 268 if opts.Publish { 269 return validate.Usage("The {0} option is not available{1}.", "--publish", " in "+mode+" mode") 270 } 271 if opts.Remote { 272 return validate.Usage("The {0} option is not available{1}.", "--remote", " in "+mode+" mode") 273 } 274 if opts.Truncate != base.NOPOSN { 275 return validate.Usage("The {0} option is not available{1}.", "--truncate", " in "+mode+" mode") 276 } 277 } 278 return nil 279 }