glazed 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +5 -0
  4. data/CHANGELOG.md +18 -0
  5. data/LICENSE.txt +202 -0
  6. data/NOTICE +16 -0
  7. data/README.md +3 -0
  8. data/Rakefile +12 -0
  9. data/exe/glazed +8 -0
  10. data/lib/glazed/cli_session.rb +178 -0
  11. data/lib/glazed/collector.rb +113 -0
  12. data/lib/glazed/command/base.rb +146 -0
  13. data/lib/glazed/command/orchestrate.rb +78 -0
  14. data/lib/glazed/command/run.rb +51 -0
  15. data/lib/glazed/command/send_signal.rb +69 -0
  16. data/lib/glazed/command/status.rb +179 -0
  17. data/lib/glazed/command/version.rb +23 -0
  18. data/lib/glazed/command.rb +39 -0
  19. data/lib/glazed/command_result.rb +26 -0
  20. data/lib/glazed/dialect/actions.rb +95 -0
  21. data/lib/glazed/dialect/communication_config.rb +63 -0
  22. data/lib/glazed/dialect/interaction.rb +32 -0
  23. data/lib/glazed/dialect/run.rb +84 -0
  24. data/lib/glazed/dialect/runbook.rb +97 -0
  25. data/lib/glazed/dialect/sensitive_pattern.rb +31 -0
  26. data/lib/glazed/dialect.rb +12 -0
  27. data/lib/glazed/dialects/awplus.rb +84 -0
  28. data/lib/glazed/dialects/base.rb +246 -0
  29. data/lib/glazed/dialects/ios.rb +173 -0
  30. data/lib/glazed/dialects/junos.rb +78 -0
  31. data/lib/glazed/dialects/nec_ix.rb +71 -0
  32. data/lib/glazed/dialects/wlc.rb +56 -0
  33. data/lib/glazed/dialects.rb +65 -0
  34. data/lib/glazed/finder.rb +62 -0
  35. data/lib/glazed/must.rb +54 -0
  36. data/lib/glazed/orchestrator/policy.rb +184 -0
  37. data/lib/glazed/orchestrator.rb +288 -0
  38. data/lib/glazed/plugin_registry.rb +70 -0
  39. data/lib/glazed/proto.rb +20 -0
  40. data/lib/glazed/proto_json.rb +34 -0
  41. data/lib/glazed/redactor.rb +158 -0
  42. data/lib/glazed/runner.rb +176 -0
  43. data/lib/glazed/secret.rb +74 -0
  44. data/lib/glazed/secrets.rb +59 -0
  45. data/lib/glazed/shell_command.rb +90 -0
  46. data/lib/glazed/sources/base.rb +79 -0
  47. data/lib/glazed/sources/command.rb +67 -0
  48. data/lib/glazed/sources/inline.rb +56 -0
  49. data/lib/glazed/sources/json_files.rb +87 -0
  50. data/lib/glazed/sources.rb +105 -0
  51. data/lib/glazed/state_backends/base.rb +72 -0
  52. data/lib/glazed/state_backends/sqlite.rb +299 -0
  53. data/lib/glazed/state_backends.rb +75 -0
  54. data/lib/glazed/step_context.rb +65 -0
  55. data/lib/glazed/steps/base.rb +89 -0
  56. data/lib/glazed/steps/batch_shell.rb +57 -0
  57. data/lib/glazed/steps/fetch.rb +67 -0
  58. data/lib/glazed/steps/publish.rb +94 -0
  59. data/lib/glazed/steps/redact.rb +77 -0
  60. data/lib/glazed/steps/shell.rb +107 -0
  61. data/lib/glazed/steps.rb +83 -0
  62. data/lib/glazed/storages/base.rb +167 -0
  63. data/lib/glazed/storages/filesystem.rb +144 -0
  64. data/lib/glazed/storages/git.rb +181 -0
  65. data/lib/glazed/storages/github.rb +269 -0
  66. data/lib/glazed/storages/memory.rb +125 -0
  67. data/lib/glazed/storages.rb +88 -0
  68. data/lib/glazed/transports/base.rb +103 -0
  69. data/lib/glazed/transports/ssh.rb +177 -0
  70. data/lib/glazed/transports/telnet.rb +100 -0
  71. data/lib/glazed/transports.rb +28 -0
  72. data/lib/glazed/v1/config_pb.rb +25 -0
  73. data/lib/glazed/v1/inventory_pb.rb +27 -0
  74. data/lib/glazed/v1/orchestrator_pb.rb +27 -0
  75. data/lib/glazed/v1/redaction_pb.rb +23 -0
  76. data/lib/glazed/v1/secret_pb.rb +22 -0
  77. data/lib/glazed/v1/state_pb.rb +25 -0
  78. data/lib/glazed/v1/steps_pb.rb +25 -0
  79. data/lib/glazed/v1/storage_pb.rb +34 -0
  80. data/lib/glazed/v1/workflow_pb.rb +29 -0
  81. data/lib/glazed/validation.rb +31 -0
  82. data/lib/glazed/version.rb +9 -0
  83. data/lib/glazed/workflow.rb +70 -0
  84. data/lib/glazed.rb +18 -0
  85. metadata +245 -0
@@ -0,0 +1,79 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ require 'json'
7
+ require 'google/protobuf'
8
+ require 'glazed'
9
+ require 'glazed/proto'
10
+ require 'glazed/proto_json'
11
+ require 'glazed/validation'
12
+
13
+ module Glazed
14
+ module Sources
15
+ # Inventory provider. Subclasses implement {.from_proto} and {#nodes}.
16
+ class Base
17
+ # Raised when inventory is malformed or a node lacks a name or dialect.
18
+ class InvalidInventory < Glazed::Error; end
19
+
20
+ # Node fields merged key by key with defaults.
21
+ MAP_FIELDS = %w[params secrets].freeze #: Array[String]
22
+
23
+ # Builds a provider from its settings message.
24
+ #
25
+ # @param settings [Google::Protobuf::MessageExts] provider settings from {Proto::V1::InventorySource}
26
+ # @return [Base] the provider
27
+ #: (Google::Protobuf::MessageExts settings) -> Base
28
+ def self.from_proto(settings) = raise NotImplementedError
29
+
30
+ # Reads the nodes.
31
+ #
32
+ # @return [Array<Proto::V1::Node>] nodes, each with a name and a dialect
33
+ #: () -> Array[Proto::V1::Node]
34
+ def nodes = raise NotImplementedError
35
+
36
+ # @return [String] where the nodes come from, for error messages
37
+ #: () -> String
38
+ def description = self.class.name.to_s
39
+
40
+ # Builds the nodes of ProtoJSON of an {Proto::V1::InventoryFile}.
41
+ #
42
+ # @param json [String] the inventory file
43
+ # @param origin [String] where the JSON comes from, for error messages
44
+ # @return [Array<Proto::V1::Node>] nodes with defaults applied
45
+ # @raise [InvalidInventory] when the JSON is malformed
46
+ # @raise [InvalidMessage] when the inventory breaks the rules of its schema
47
+ #: (String json, origin: String) -> Array[Proto::V1::Node]
48
+ private def nodes_from_json(json, origin:)
49
+ Validation.validate!(ProtoJson.load(Proto::V1::InventoryFile, json), origin:)
50
+ document = JSON.parse(json) #: Hash[String, untyped]
51
+ nodes_from(document, origin:)
52
+ rescue Google::Protobuf::ParseError, JSON::ParserError => e
53
+ raise InvalidInventory, "#{origin}: #{e.message}"
54
+ end
55
+
56
+ # Builds the nodes of an {Proto::V1::InventoryFile} document, filling
57
+ # fields each node leaves unset from its defaults. The params and secrets
58
+ # maps are merged key by key.
59
+ #
60
+ # @param document [Hash{String => Object}] the inventory file as decoded JSON
61
+ # @param origin [String] where the document comes from, for error messages
62
+ # @return [Array<Proto::V1::Node>] the nodes
63
+ # @raise [InvalidInventory] when a node is malformed
64
+ #: (Hash[String, untyped] document, origin: String) -> Array[Proto::V1::Node]
65
+ private def nodes_from(document, origin:)
66
+ defaults = document.fetch('defaults', {}) #: Hash[String, untyped]
67
+ nodes = document.fetch('nodes', []) #: Array[Hash[String, untyped]]
68
+ nodes.map do |attributes|
69
+ merged = defaults.merge(attributes) do |key, default, value|
70
+ MAP_FIELDS.include?(key) ? default.merge(value) : value
71
+ end
72
+ ProtoJson.load(Proto::V1::Node, JSON.generate(merged))
73
+ rescue Google::Protobuf::ParseError => e
74
+ raise InvalidInventory, "#{origin}: #{e.message}"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,67 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ require 'glazed'
7
+ require 'glazed/must'
8
+ require 'glazed/proto'
9
+ require 'glazed/shell_command'
10
+ require 'glazed/sources'
11
+ require 'glazed/sources/base'
12
+
13
+ module Glazed
14
+ module Sources
15
+ # Inventory printed by a command as ProtoJSON of {Proto::V1::InventoryFile},
16
+ # for nodes kept in another system.
17
+ #
18
+ # @example
19
+ # Glazed::Sources::Command.new(run: 'bin/inventory --site hnd').nodes
20
+ class Command < Base
21
+ # Seconds before the command is killed when the settings set none.
22
+ DEFAULT_TIMEOUT = 300 #: Integer
23
+
24
+ # @param run [String] command line, run by /bin/sh -c
25
+ # @param timeout [Numeric] seconds before the command is killed
26
+ #: (run: String, ?timeout: Numeric) -> void
27
+ def initialize(run:, timeout: DEFAULT_TIMEOUT)
28
+ super()
29
+ @run = run #: String
30
+ @timeout = timeout #: Numeric
31
+ end
32
+
33
+ # Builds a provider from {Proto::V1::CommandInventory}.
34
+ #
35
+ # @param settings [Google::Protobuf::MessageExts] a {Proto::V1::CommandInventory}
36
+ # @return [Command] the provider
37
+ # @raise [Must::Violation] when settings is another message
38
+ #: (Google::Protobuf::MessageExts settings) -> Command
39
+ def self.from_proto(settings)
40
+ command = Must.be_a(settings, Proto::V1::CommandInventory)
41
+ new(run: command.run, timeout: command.timeout_seconds.zero? ? DEFAULT_TIMEOUT : command.timeout_seconds)
42
+ end
43
+
44
+ # @return [String] command line
45
+ #: String
46
+ attr_reader :run
47
+
48
+ # @return [Numeric] seconds before the command is killed
49
+ #: Numeric
50
+ attr_reader :timeout
51
+
52
+ # Runs the command and reads the nodes it prints.
53
+ #
54
+ # @return [Array<Proto::V1::Node>] nodes with defaults applied
55
+ # @raise [ShellCommand::Failed] when the command exits non-zero
56
+ # @raise [TimeoutError] when the command does not finish in time
57
+ # @raise [InvalidInventory] when the output is not an inventory file
58
+ # @raise [InvalidMessage] when the output breaks the rules of its schema
59
+ #: () -> Array[Proto::V1::Node]
60
+ def nodes = nodes_from_json(ShellCommand.new(run:, env: {}, stdin: '', timeout:).call, origin: description)
61
+
62
+ # @return [String] the command line
63
+ #: () -> String
64
+ def description = "command #{run}"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,56 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ require 'json'
7
+ require 'glazed/must'
8
+ require 'glazed/proto'
9
+ require 'glazed/proto_json'
10
+ require 'glazed/sources'
11
+ require 'glazed/sources/base'
12
+ require 'glazed/validation'
13
+
14
+ module Glazed
15
+ module Sources
16
+ # Inventory written in the config itself, for setups that keep everything
17
+ # in one file. Defaults apply as in {JsonFiles}.
18
+ #
19
+ # @example Inventory source
20
+ # {"inline": {"defaults": {"dialect": "ios"}, "nodes": [{"name": "rt1"}]}}
21
+ class Inline < Base
22
+ # @param inventory [Proto::V1::InventoryFile] nodes and their defaults
23
+ #: (inventory: Proto::V1::InventoryFile) -> void
24
+ def initialize(inventory:)
25
+ super()
26
+ @inventory = inventory #: Proto::V1::InventoryFile
27
+ end
28
+
29
+ # Builds a provider from an inline {Proto::V1::InventoryFile}.
30
+ #
31
+ # @param settings [Google::Protobuf::MessageExts] a {Proto::V1::InventoryFile}
32
+ # @return [Inline] the provider
33
+ # @raise [Must::Violation] when settings is another message
34
+ # @raise [InvalidMessage] when the inventory breaks the rules of its schema
35
+ #: (Google::Protobuf::MessageExts settings) -> Inline
36
+ def self.from_proto(settings)
37
+ inventory = Must.be_a(settings, Proto::V1::InventoryFile)
38
+ Validation.validate!(inventory, origin: 'inline inventory')
39
+ new(inventory:)
40
+ end
41
+
42
+ # @return [Array<Proto::V1::Node>] the nodes with defaults applied
43
+ # @raise [InvalidInventory] when a node lacks a name or dialect
44
+ #: () -> Array[Proto::V1::Node]
45
+ def nodes
46
+ # ProtoJSON omits unset fields, so defaults merge as they do for files.
47
+ document = JSON.parse(ProtoJson.dump(@inventory)) #: Hash[String, untyped]
48
+ nodes_from(document, origin: description)
49
+ end
50
+
51
+ # @return [String] where the nodes come from
52
+ #: () -> String
53
+ def description = 'inline inventory'
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,87 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ require 'glazed'
7
+ require 'glazed/must'
8
+ require 'glazed/proto'
9
+ require 'glazed/sources'
10
+ require 'glazed/sources/base'
11
+
12
+ module Glazed
13
+ module Sources
14
+ # Inventory read from ProtoJSON files of {Proto::V1::InventoryFile}.
15
+ #
16
+ # Each file's `defaults` fill fields its nodes leave unset. The `params`
17
+ # and `secrets` maps are merged key by key.
18
+ #
19
+ # @example
20
+ # Glazed::Sources::JsonFiles.new(paths: ['inventory/*.json']).nodes
21
+ class JsonFiles < Base
22
+ # Raised when a file is not a valid inventory file.
23
+ InvalidFile = Base::InvalidInventory
24
+
25
+ # @param paths [Array<String>] file paths or glob patterns
26
+ #: (paths: Array[String]) -> void
27
+ def initialize(paths:)
28
+ super()
29
+ @paths = paths #: Array[String]
30
+ end
31
+
32
+ # Builds a provider from {Proto::V1::JsonFilesInventory}.
33
+ #
34
+ # @param settings [Google::Protobuf::MessageExts] a {Proto::V1::JsonFilesInventory}
35
+ # @return [JsonFiles] the provider
36
+ # @raise [Must::Violation] when settings is another message
37
+ #: (Google::Protobuf::MessageExts settings) -> JsonFiles
38
+ def self.from_proto(settings)
39
+ new(paths: Must.be_a(settings, Proto::V1::JsonFilesInventory).paths.to_a)
40
+ end
41
+
42
+ # @return [Array<String>] file paths or glob patterns
43
+ #: Array[String]
44
+ attr_reader :paths
45
+
46
+ # Reads every file and returns its nodes.
47
+ #
48
+ # @return [Array<Proto::V1::Node>] nodes in file order
49
+ # @raise [InvalidFile] when a file is malformed or a node lacks a name or dialect
50
+ # @raise [Sources::DuplicateNode] when a node name appears twice
51
+ # @raise [Errno::ENOENT] when a literal path does not exist
52
+ #: () -> Array[Proto::V1::Node]
53
+ def nodes
54
+ origins = {} #: Hash[String, String]
55
+ files.flat_map do |path|
56
+ load_file(path).each do |node|
57
+ if (other = origins[node.name])
58
+ raise DuplicateNode, "node #{node.name.inspect} is defined in both #{other} and #{path}"
59
+ end
60
+
61
+ origins[node.name] = path
62
+ end
63
+ end
64
+ end
65
+
66
+ # @return [String] the path patterns
67
+ #: () -> String
68
+ def description = "json_files #{paths.join(', ')}"
69
+
70
+ # @return [Array<String>] files matched by {#paths}, in a stable order
71
+ #: () -> Array[String]
72
+ private def files
73
+ paths.flat_map do |pattern|
74
+ matches = Dir.glob(pattern).sort
75
+ matches.empty? && !pattern.match?(/[*?\[{]/) ? [pattern] : matches
76
+ end.uniq
77
+ end
78
+
79
+ # @param path [String] inventory file
80
+ # @return [Array<Proto::V1::Node>] nodes with defaults applied
81
+ # @raise [InvalidFile] when the file is malformed
82
+ # @raise [InvalidMessage] when the file breaks the rules of its schema
83
+ #: (String path) -> Array[Proto::V1::Node]
84
+ private def load_file(path) = nodes_from_json(File.read(path), origin: path)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,105 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ require 'glazed'
7
+ require 'glazed/finder'
8
+ require 'glazed/must'
9
+ require 'glazed/plugin_registry'
10
+ require 'glazed/proto'
11
+ require 'glazed/sources/base'
12
+ require 'glazed/validation'
13
+
14
+ module Glazed
15
+ # Namespace of inventory providers, looked up by name.
16
+ module Sources
17
+ # Raised when an inventory source names no provider.
18
+ class MissingProvider < Glazed::Error; end
19
+ # Raised when two nodes share a name.
20
+ class DuplicateNode < Glazed::Error; end
21
+
22
+ # Finds a provider class by snake_case name.
23
+ #
24
+ # @param name [String, Symbol] provider name, such as "json_files"
25
+ # @return [Class<Base>] the provider class
26
+ # @raise [Finder::NotFound] when there is none, for example when the gem providing it is not installed
27
+ # @raise [Must::Violation] when the name resolves to something other than a provider
28
+ #: (String | Symbol name) -> singleton(Base)
29
+ def self.find(name)
30
+ found = Finder.find(self, 'glazed/sources', name)
31
+ return found if found.is_a?(Class) && found < Base
32
+
33
+ raise Must::Violation, "#{found} is not an inventory provider"
34
+ end
35
+
36
+ # Inventory providers implemented by plugins, by settings message type.
37
+ PLUGINS = PluginRegistry.new('inventory provider') #: PluginRegistry
38
+
39
+ # Registers a plugin provider, selected by `plugin` sources whose settings are of the given type.
40
+ #
41
+ # @param settings_class [Class] generated message class of the provider settings
42
+ # @param implementation [Class<Base>] provider built with {Base.from_proto}
43
+ # @return [void]
44
+ # @example
45
+ # Glazed::Sources.register(GlazedNetbox::V1::NetboxInventory, GlazedNetbox::Source)
46
+ #: (singleton(Google::Protobuf::AbstractMessage) settings_class, singleton(Base) implementation) -> void
47
+ def self.register(settings_class, implementation)
48
+ PLUGINS.register(settings_class, implementation)
49
+ end
50
+
51
+ # Builds the provider a source selects.
52
+ #
53
+ # @param source [Proto::V1::InventorySource] provider settings
54
+ # @return [Base] the provider
55
+ # @raise [InvalidMessage] when the source breaks the rules of its schema
56
+ # @raise [Finder::NotFound] when the provider is not available
57
+ # @raise [PluginRegistry::UnknownPlugin] when no plugin is registered for the settings type
58
+ #: (Proto::V1::InventorySource source) -> Base
59
+ def self.build(source)
60
+ Validation.validate!(source)
61
+ provider = source.provider or raise MissingProvider, 'inventory source sets no provider'
62
+ if (packed = source.plugin)
63
+ implementation, settings = PLUGINS.resolve(packed)
64
+ return provider_class(implementation).from_proto(settings)
65
+ end
66
+
67
+ settings = Must.be_a(source[provider.to_s], Google::Protobuf::MessageExts)
68
+ find(provider).from_proto(settings)
69
+ end
70
+
71
+ # @param implementation [Module] registered implementation
72
+ # @return [Class<Base>] the implementation as a provider class
73
+ # @raise [Must::Violation] when it is not a provider
74
+ #: (Module[untyped] implementation) -> singleton(Base)
75
+ private_class_method def self.provider_class(implementation)
76
+ return implementation if implementation.is_a?(Class) && implementation < Base
77
+
78
+ raise Must::Violation, "#{implementation} is not an inventory provider"
79
+ end
80
+
81
+ # Reads the nodes of every source.
82
+ #
83
+ # @param sources [Array<Proto::V1::InventorySource>] sources in order
84
+ # @return [Array<Proto::V1::Node>] nodes in source order
85
+ # @raise [DuplicateNode] when a node name appears twice
86
+ # @raise [Base::InvalidInventory] when a node lacks a name or dialect
87
+ # @raise [InvalidMessage] when a source breaks the rules of its schema
88
+ #: (Array[Proto::V1::InventorySource] sources) -> Array[Proto::V1::Node]
89
+ def self.nodes(sources)
90
+ origins = {} #: Hash[String, String]
91
+ sources.flat_map do |source|
92
+ provider = build(source)
93
+ provider.nodes.each do |node|
94
+ raise Base::InvalidInventory, "#{provider.description}: a node has no name" if node.name.empty?
95
+ raise Base::InvalidInventory, "#{provider.description}: node #{node.name.inspect} has no dialect" if node.dialect.empty?
96
+ if (other = origins[node.name])
97
+ raise DuplicateNode, "node #{node.name.inspect} is defined in both #{other} and #{provider.description}"
98
+ end
99
+
100
+ origins[node.name] = provider.description
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,72 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ require 'google/protobuf'
7
+ require 'glazed'
8
+ require 'glazed/proto'
9
+
10
+ module Glazed
11
+ module StateBackends
12
+ # Store of {Proto::V1::NodeSchedule} records, one per workflow and node.
13
+ # Records change only through read-modify-write blocks, which a backend may
14
+ # run more than once when another process changed the records meanwhile,
15
+ # so a block must compute its result from the record it is given alone.
16
+ # Subclasses implement {.from_proto}, {#update_all}, {#list_due} and {#list}.
17
+ class Base
18
+ #: type updater = ^(String node, Proto::V1::NodeSchedule? record) -> Proto::V1::NodeSchedule?
19
+
20
+ # Builds a state backend from its settings message.
21
+ #
22
+ # @param settings [Google::Protobuf::MessageExts] backend settings from {Proto::V1::StateBackendConfig}
23
+ # @param context [StepContext] configured storages, and where progress is reported
24
+ # @return [Base] the state backend
25
+ #: (Google::Protobuf::MessageExts settings, context: StepContext) -> Base
26
+ def self.from_proto(settings, context:) = raise NotImplementedError
27
+
28
+ # Replaces the records of nodes with what the block returns.
29
+ #
30
+ # @param workflow [String] workflow name
31
+ # @param nodes [Array<String>] node names
32
+ # @yieldparam node [String] node name
33
+ # @yieldparam record [Proto::V1::NodeSchedule, nil] the current record, or nil when there is none
34
+ # @yieldreturn [Proto::V1::NodeSchedule, nil] the new record, or nil to delete it
35
+ # @return [Hash{String => Proto::V1::NodeSchedule}] records held afterwards, by node name
36
+ #: (String workflow, Array[String] nodes) { (String node, Proto::V1::NodeSchedule? record) -> Proto::V1::NodeSchedule? } -> Hash[String, Proto::V1::NodeSchedule]
37
+ def update_all(workflow, nodes, &) = raise NotImplementedError
38
+
39
+ # Replaces the record of one node with what the block returns.
40
+ #
41
+ # @param workflow [String] workflow name
42
+ # @param node [String] node name
43
+ # @yieldparam record [Proto::V1::NodeSchedule, nil] the current record, or nil when there is none
44
+ # @yieldreturn [Proto::V1::NodeSchedule, nil] the new record, or nil to delete it
45
+ # @return [Proto::V1::NodeSchedule, nil] the record held afterwards
46
+ #: (String workflow, String node) { (Proto::V1::NodeSchedule? record) -> Proto::V1::NodeSchedule? } -> Proto::V1::NodeSchedule?
47
+ def update(workflow, node, &block)
48
+ update_all(workflow, [node]) { |_node, record| block.call(record) }[node]
49
+ end
50
+
51
+ # Records due at or before a time and not leased to an orchestrator.
52
+ #
53
+ # @param workflow [String] workflow name
54
+ # @param now [Time] the time; a lease ending at or before it has expired
55
+ # @param limit [Integer, nil] records returned at most; all when nil
56
+ # @return [Array<Proto::V1::NodeSchedule>] the records, earliest due first
57
+ #: (String workflow, now: Time, ?limit: Integer?) -> Array[Proto::V1::NodeSchedule]
58
+ def list_due(workflow, now:, limit: nil) = raise NotImplementedError
59
+
60
+ # @param workflow [String] workflow name
61
+ # @return [Array<Proto::V1::NodeSchedule>] every record of the workflow, by node name
62
+ #: (String workflow) -> Array[Proto::V1::NodeSchedule]
63
+ def list(workflow) = raise NotImplementedError
64
+
65
+ # Releases resources held by the backend.
66
+ #
67
+ # @return [void]
68
+ #: () -> void
69
+ def close; end
70
+ end
71
+ end
72
+ end