wavefront-cli 5.1.1 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +37 -1
  3. data/HISTORY.md +34 -2
  4. data/README.md +2 -4
  5. data/lib/wavefront-cli/account.rb +119 -0
  6. data/lib/wavefront-cli/alert.rb +29 -0
  7. data/lib/wavefront-cli/base.rb +0 -2
  8. data/lib/wavefront-cli/cloudintegration.rb +12 -0
  9. data/lib/wavefront-cli/commands/.rubocop.yml +34 -0
  10. data/lib/wavefront-cli/commands/account.rb +61 -0
  11. data/lib/wavefront-cli/commands/alert.rb +1 -0
  12. data/lib/wavefront-cli/commands/base.rb +1 -1
  13. data/lib/wavefront-cli/commands/cloudintegration.rb +4 -1
  14. data/lib/wavefront-cli/commands/proxy.rb +2 -1
  15. data/lib/wavefront-cli/commands/query.rb +4 -1
  16. data/lib/wavefront-cli/commands/role.rb +44 -0
  17. data/lib/wavefront-cli/commands/spy.rb +0 -5
  18. data/lib/wavefront-cli/commands/usergroup.rb +7 -11
  19. data/lib/wavefront-cli/commands/write.rb +7 -2
  20. data/lib/wavefront-cli/controller.rb +5 -63
  21. data/lib/wavefront-cli/display/account.rb +122 -0
  22. data/lib/wavefront-cli/display/alert.rb +8 -0
  23. data/lib/wavefront-cli/display/base.rb +1 -1
  24. data/lib/wavefront-cli/display/cloudintegration.rb +15 -2
  25. data/lib/wavefront-cli/display/printer/long.rb +2 -1
  26. data/lib/wavefront-cli/display/proxy.rb +16 -0
  27. data/lib/wavefront-cli/display/role.rb +66 -0
  28. data/lib/wavefront-cli/display/settings.rb +1 -0
  29. data/lib/wavefront-cli/display/usergroup.rb +18 -14
  30. data/lib/wavefront-cli/exception_handler.rb +89 -0
  31. data/lib/wavefront-cli/output/hcl/base.rb +1 -1
  32. data/lib/wavefront-cli/output/hcl/dashboard.rb +1 -1
  33. data/lib/wavefront-cli/proxy.rb +5 -0
  34. data/lib/wavefront-cli/query.rb +13 -7
  35. data/lib/wavefront-cli/role.rb +54 -0
  36. data/lib/wavefront-cli/serviceaccount.rb +0 -6
  37. data/lib/wavefront-cli/spy.rb +0 -8
  38. data/lib/wavefront-cli/usergroup.rb +8 -8
  39. data/lib/wavefront-cli/version.rb +1 -1
  40. data/lib/wavefront-cli/write.rb +29 -5
  41. data/spec/.rubocop.yml +34 -0
  42. data/spec/test_mixins/delete.rb +1 -2
  43. data/spec/wavefront-cli/account_spec.rb +303 -0
  44. data/spec/wavefront-cli/alert_spec.rb +28 -0
  45. data/spec/wavefront-cli/cloudintegration_spec.rb +19 -6
  46. data/spec/wavefront-cli/commands/write_spec.rb +1 -1
  47. data/spec/wavefront-cli/event_spec.rb +1 -1
  48. data/spec/wavefront-cli/output/csv/query_spec.rb +1 -1
  49. data/spec/wavefront-cli/output/wavefront/query_spec.rb +2 -2
  50. data/spec/wavefront-cli/query_spec.rb +20 -3
  51. data/spec/wavefront-cli/role_spec.rb +187 -0
  52. data/spec/wavefront-cli/serviceaccount_spec.rb +3 -3
  53. data/spec/wavefront-cli/usergroup_spec.rb +48 -43
  54. data/spec/wavefront-cli/write_spec.rb +44 -0
  55. data/wavefront-cli.gemspec +3 -3
  56. metadata +28 -36
  57. data/lib/wavefront-cli/commands/cluster.rb +0 -44
  58. data/lib/wavefront-cli/commands/user.rb +0 -54
  59. data/lib/wavefront-cli/display/monitoredcluster.rb +0 -14
  60. data/lib/wavefront-cli/display/user.rb +0 -103
  61. data/lib/wavefront-cli/monitoredcluster.rb +0 -50
  62. data/lib/wavefront-cli/user.rb +0 -92
  63. data/spec/wavefront-cli/monitoredcluster_spec.rb +0 -85
  64. data/spec/wavefront-cli/resources/responses/user-list.json +0 -1
  65. data/spec/wavefront-cli/user_spec.rb +0 -311
@@ -26,7 +26,10 @@ class WavefrontCommandCloudintegration < WavefrontCommandBase
26
26
  "disable #{CMN} <id>",
27
27
  "dump #{CMN}",
28
28
  "import #{CMN} [-uU] <file>",
29
- "search #{CMN} [-al] [-o offset] [-L limit] [-O fields] <condition>..."]
29
+ "search #{CMN} [-al] [-o offset] [-L limit] [-O fields] <condition>...",
30
+ "awsid #{CMN} generate",
31
+ "awsid #{CMN} delete <external_id>",
32
+ "awsid #{CMN} confirm <external_id>"]
30
33
  end
31
34
 
32
35
  def _options
@@ -10,7 +10,7 @@ class WavefrontCommandProxy < WavefrontCommandBase
10
10
  end
11
11
 
12
12
  def _commands
13
- ["list #{CMN} [-al] [-O fields] [-o offset] [-L limit]",
13
+ ["list #{CMN} [-laA] [-O fields] [-o offset] [-L limit]",
14
14
  "describe #{CMN} <id>",
15
15
  "delete #{CMN} <id>",
16
16
  "undelete #{CMN} <id>",
@@ -23,6 +23,7 @@ class WavefrontCommandProxy < WavefrontCommandBase
23
23
  [common_options,
24
24
  "-l, --long list #{things} in detail",
25
25
  "-a, --all list all #{things}",
26
+ "-A, --active only show active #{things}",
26
27
  "-o, --offset=n start from nth #{thing}",
27
28
  '-O, --fields=F1,F2,... only show given fields',
28
29
  "-L, --limit=COUNT number of #{things} to list"]
@@ -12,7 +12,7 @@ class WavefrontCommandQuery < WavefrontCommandBase
12
12
  def _commands
13
13
  ['aliases [-DV] [-c file] [-P profile]',
14
14
  "#{CMN} [-g granularity] [-s time] [-e time] " \
15
- '[-WikvO] [-S mode] [-N name] [-p points] [-F options] <query>',
15
+ '[-ikvCGKOW] [-S mode] [-N name] [-p points] [-F options] <query>',
16
16
  "raw #{CMN} [-H host] [-s time] [-e time] " \
17
17
  '[-F options] <metric>',
18
18
  "run #{CMN} [-g granularity] [-s time] [-e time] " \
@@ -36,6 +36,9 @@ class WavefrontCommandQuery < WavefrontCommandBase
36
36
  '-F, --format-opts=STRING comma-separated options to pass to ' \
37
37
  'output formatter',
38
38
  '-k, --nospark do not show sparkline',
39
+ '-C, --nocache do not use the query cache',
40
+ '-K, --nostrict allow points outside the query window',
41
+ '-G, --histogram-view use histogram view rather than metric',
39
42
  '-W, --nowarn do not show API warning messages']
40
43
  end
41
44
 
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ # Define the 'role' command.
6
+ #
7
+ class WavefrontCommandRole < WavefrontCommandBase
8
+ def _commands
9
+ ["list #{CMN} [-al] [-O fields] [-o offset] [-L limit]",
10
+ "describe #{CMN} <id>",
11
+ "create #{CMN} [-d description] [-p permission...] <name>",
12
+ "delete #{CMN} <id>",
13
+ "dump #{CMN}",
14
+ "import #{CMN} [-uU] <file>",
15
+ "set #{CMN} <key=value> <id>",
16
+ "accounts #{CMN} <id>",
17
+ "groups #{CMN} <id>",
18
+ "permissions #{CMN} <id>",
19
+ "give #{CMN} <id> to <member>...",
20
+ "take #{CMN} <id> from <member>...",
21
+ "grant #{CMN} <permission> to <id>",
22
+ "revoke #{CMN} <permission> from <id>",
23
+ "search #{CMN} [-al] [-o offset] [-L limit] [-O fields] <condition>..."]
24
+ end
25
+
26
+ def _options
27
+ [common_options,
28
+ "-l, --long list #{things} in detail",
29
+ "-a, --all list all #{things}",
30
+ "-o, --offset=n start from nth #{thing}",
31
+ "-L, --limit=COUNT number of #{things} to list",
32
+ '-O, --fields=F1,F2,... only show given fields',
33
+ "-u, --update update an existing #{thing}",
34
+ "-U, --upsert import new or update existing #{thing}",
35
+ "-d, --description=STRING description of #{thing}",
36
+ '-p, --permission=STRING Wavefront permission']
37
+ end
38
+
39
+ def postscript
40
+ "A role 'member' can be an account ID or a usergroup ID. 'wf settings " \
41
+ "list permissions' will give you a list of all currently supported " \
42
+ 'permissions.'.fold(TW, 0)
43
+ end
44
+ end
@@ -34,9 +34,4 @@ class WavefrontCommandSpy < WavefrontCommandBase
34
34
  '-T, --tag-key=TAG only show metrics with the given point tag key',
35
35
  '-y, --type=STRING one of METRIC, SPAN, HOST, or STRING']
36
36
  end
37
-
38
- def postscript
39
- "\nNOTE: This command uses the unofficial 'spy' API endpoint, which " \
40
- 'is not guaranteed to remain stable.'.cmd_fold(TW, 0)
41
- end
42
37
  end
@@ -24,17 +24,18 @@ class WavefrontCommandUsergroup < WavefrontCommandBase
24
24
  def _commands
25
25
  ["list #{CMN} [-al] [-O fields] [-o offset] [-L limit]",
26
26
  "describe #{CMN} <id>",
27
- "create #{CMN} [-p permission...] <name>",
27
+ "create #{CMN} [-r role_id...] <name>",
28
28
  "delete #{CMN} <id>",
29
29
  "dump #{CMN}",
30
30
  "import #{CMN} [-uU] <file>",
31
31
  "set #{CMN} <key=value> <id>",
32
+ "add to #{CMN} <id> <user>...",
33
+ "remove from #{CMN} <id> <user>...",
32
34
  "users #{CMN} <id>",
35
+ "add role #{CMN} <id> <role>...",
36
+ "remove role #{CMN} <id> <role>...",
37
+ "roles #{CMN} <id>",
33
38
  "permissions #{CMN} <id>",
34
- "add user #{CMN} <id> <user>...",
35
- "remove user #{CMN} <id> <user>...",
36
- "grant #{CMN} <permission> to <id>",
37
- "revoke #{CMN} <permission> from <id>",
38
39
  "search #{CMN} [-al] [-o offset] [-L limit] [-O fields] <condition>..."]
39
40
  end
40
41
 
@@ -47,11 +48,6 @@ class WavefrontCommandUsergroup < WavefrontCommandBase
47
48
  '-O, --fields=F1,F2,... only show given fields',
48
49
  "-u, --update update an existing #{thing}",
49
50
  "-U, --upsert import new or update existing #{thing}",
50
- '-p, --permission=STRING Wavefront permission']
51
- end
52
-
53
- def postscript
54
- "'wf settings list permissions' will give you a list of all " \
55
- 'currently supported permissions.'.fold(TW, 0)
51
+ '-r, --role-id=STRING Wavefront role ID']
56
52
  end
57
53
  end
@@ -18,7 +18,9 @@ class WavefrontCommandWrite < WavefrontCommandBase
18
18
  '<metric> [--] <val>...',
19
19
  'file [-DnViq] [-c file] [-P profile] [-E proxy] [-H host] ' \
20
20
  '[-p port] [-F infileformat] [-m metric] [-T tag...] [-I interval] ' \
21
- '[-u method] [-S socket] <file>']
21
+ '[-u method] [-S socket] <file>',
22
+ 'noise [-DnViq] [-P profile] [-E proxy] [-H host] [-p port] ' \
23
+ '[-T tag...] [-I interval] [-x value] [-X value] <metric>']
22
24
  end
23
25
 
24
26
  def _options
@@ -33,9 +35,12 @@ class WavefrontCommandWrite < WavefrontCommandBase
33
35
  'a file will be assigned. If the file contains a metric name, ' \
34
36
  'the two will be dot-concatenated, with this value first',
35
37
  '-i, --delta increment metric by given value',
36
- "-I, --interval=INTERVAL interval of distribution (default 'm')",
38
+ "-I, --interval=INTERVAL interval of distribution (default 'm'), or " \
39
+ 'time in seconds between noise values (default 1)',
37
40
  '-u, --using=METHOD method by which to send points',
38
41
  '-S, --socket=FILE Unix datagram socket',
42
+ '-x, --min=NUMERIC lower bound of random values (default -10)',
43
+ '-X, --max=NUMERIC upper bound of random values (default 10)',
39
44
  "-q, --quiet don't report the points sent summary " \
40
45
  '(unless there were errors)']
41
46
  end
@@ -17,6 +17,7 @@ require_relative 'version'
17
17
  require_relative 'constants'
18
18
  require_relative 'exception'
19
19
  require_relative 'opt_handler'
20
+ require_relative 'exception_handler'
20
21
  require_relative 'stdlib/string'
21
22
 
22
23
  CMD_DIR = Pathname.new(__dir__) + 'commands'
@@ -28,6 +29,7 @@ class WavefrontCliController
28
29
  attr_reader :args, :usage, :opts, :cmds, :tw
29
30
 
30
31
  include WavefrontCli::Constants
32
+ include WavefrontCli::ExceptionMixins
31
33
 
32
34
  def initialize(args)
33
35
  @args = args
@@ -107,12 +109,8 @@ class WavefrontCliController
107
109
  #
108
110
  def cli_class(cmd, opts)
109
111
  load_cli_class(cmd, opts)
110
- rescue WavefrontCli::Exception::UnhandledCommand
111
- abort 'Fatal error. Unsupported command. Please open a Github issue.'
112
- rescue WavefrontCli::Exception::InvalidInput => e
113
- abort "Invalid input. #{e.message}"
114
- rescue RuntimeError => e
115
- abort "Unable to run command. #{e.message}."
112
+ rescue StandardError => e
113
+ exception_handler(e)
116
114
  end
117
115
 
118
116
  def load_cli_class(cmd, opts)
@@ -120,68 +118,12 @@ class WavefrontCliController
120
118
  Object.const_get('WavefrontCli').const_get(cmds[cmd].sdk_class).new(opts)
121
119
  end
122
120
 
123
- # rubocop:disable Metrics/MethodLength
124
- # rubocop:disable Metrics/AbcSize
125
121
  def run_command(cli_class_obj)
126
122
  cli_class_obj.validate_opts
127
123
  cli_class_obj.run
128
- rescue Interrupt
129
- abort "\nOperation aborted at user request."
130
- rescue WavefrontCli::Exception::ConfigFileNotFound => e
131
- abort "Configuration file #{e}' not found."
132
- rescue WavefrontCli::Exception::CredentialError => e
133
- handle_missing_credentials(e)
134
- rescue WavefrontCli::Exception::MandatoryValue
135
- abort 'A value must be supplied.'
136
- rescue Wavefront::Exception::NetworkTimeout
137
- abort 'Connection timed out.'
138
- rescue Wavefront::Exception::InvalidPermission => e
139
- abort "'#{e}' is not a valid privilege."
140
- rescue Wavefront::Exception::InvalidUserGroupId => e
141
- abort "'#{e}' is not a valid user group id."
142
- rescue WavefrontCli::Exception::InvalidValue => e
143
- abort "Invalid value for #{e}."
144
- rescue WavefrontCli::Exception::ProfileExists => e
145
- abort "Profile '#{e}' already exists."
146
- rescue WavefrontCli::Exception::ProfileNotFound => e
147
- abort "Profile '#{e}' not found."
148
- rescue WavefrontCli::Exception::FileNotFound
149
- abort 'File not found.'
150
- rescue WavefrontCli::Exception::InsufficientData => e
151
- abort "Insufficient data. #{e.message}"
152
- rescue WavefrontCli::Exception::InvalidQuery => e
153
- abort "Invalid query. API message: '#{e.message}'."
154
- rescue WavefrontCli::Exception::SystemError => e
155
- abort "Host system error. #{e.message}"
156
- rescue WavefrontCli::Exception::UnparseableInput => e
157
- abort "Cannot parse input. #{e.message}"
158
- rescue WavefrontCli::Exception::UnparseableSearchPattern
159
- abort 'Searches require a key, a value, and a match operator.'
160
- rescue WavefrontCli::Exception::UnsupportedFileFormat
161
- abort 'Unsupported file format.'
162
- rescue WavefrontCli::Exception::UnsupportedOperation => e
163
- abort "Unsupported operation.\n#{e.message}"
164
- rescue WavefrontCli::Exception::UnsupportedOutput => e
165
- abort e.message
166
- rescue WavefrontCli::Exception::UnsupportedNoop
167
- abort 'Multiple API call operations cannot be performed as no-ops.'
168
- rescue WavefrontCli::Exception::UserGroupNotFound => e
169
- abort "Cannot find user group '#{e.message}'."
170
- rescue Wavefront::Exception::UnsupportedWriter => e
171
- abort "Unsupported writer '#{e.message}'."
172
- rescue WavefrontCli::Exception::UserError => e
173
- abort "User error: #{e.message}."
174
- rescue WavefrontCli::Exception::ImpossibleSearch
175
- abort 'Search on non-existent key. Please use a top-level field.'
176
- rescue Wavefront::Exception::InvalidSamplingValue
177
- abort 'Sampling rates must be between 0 and 0.05.'
178
124
  rescue StandardError => e
179
- warn "general error: #{e}"
180
- backtrace_message(e)
181
- abort
125
+ exception_handler(e)
182
126
  end
183
- # rubocop:enable Metrics/MethodLength
184
- # rubocop:enable Metrics/AbcSize
185
127
 
186
128
  def backtrace_message(err)
187
129
  if opts[:debug]
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module WavefrontDisplay
6
+ #
7
+ # Format human-readable output for account management.
8
+ #
9
+ class Account < Base
10
+ def do_list_brief
11
+ filter_user_list
12
+ puts(data.map { |account| account[:identifier] })
13
+ end
14
+
15
+ def do_list
16
+ filter_user_list
17
+ super
18
+ end
19
+
20
+ def do_role_add_to
21
+ puts format("Gave %<quoted_roles>s to '%<id>s'.",
22
+ id: options[:'<id>'],
23
+ quoted_roles: quoted(options[:'<role>']))
24
+ end
25
+
26
+ def do_role_remove_from
27
+ puts format("Removed %<quoted_roles>s from '%<id>s'.",
28
+ id: options[:'<id>'],
29
+ quoted_roles: quoted(options[:'<role>']))
30
+ end
31
+
32
+ def do_roles
33
+ roles = data.fetch(:roles, [])
34
+ puts roles.empty? ? "'#{options[:'<id>']}' has no roles." : roles
35
+ end
36
+
37
+ def do_group_add_to
38
+ puts format("Added '%<id>s' to %<quoted_group>s.",
39
+ id: options[:'<id>'],
40
+ quoted_group: quoted(options[:'<group>']))
41
+ end
42
+
43
+ def do_group_remove_from
44
+ puts format("Removed '%<id>s' from %<quoted_group>s.",
45
+ id: options[:'<id>'],
46
+ quoted_group: quoted(options[:'<group>']))
47
+ end
48
+
49
+ def do_groups
50
+ groups = data.fetch(:userGroups, [])
51
+
52
+ if groups.empty?
53
+ puts "'#{options[:'<id>']}' does not belong to any groups."
54
+ else
55
+ puts groups.sort
56
+ end
57
+ end
58
+
59
+ def do_business_functions
60
+ puts data.sort
61
+ end
62
+
63
+ def do_grant_to
64
+ puts format("Granted '%<permission>s' to %<quoted_accounts>s.",
65
+ permission: options[:'<permission>'],
66
+ quoted_accounts: quoted(options[:'<account>']))
67
+ end
68
+
69
+ def do_revoke_from
70
+ puts format("Revoked '%<permission>s' from %<quoted_accounts>s.",
71
+ permission: options[:'<permission>'],
72
+ quoted_accounts: quoted(options[:'<account>']))
73
+ end
74
+
75
+ def do_permissions
76
+ perms = data.fetch(:groups, [])
77
+
78
+ if perms.empty?
79
+ puts "'#{options[:'<id>']}' does not have any permissions directly " \
80
+ 'attached.'
81
+ else
82
+ puts perms.sort
83
+ end
84
+ end
85
+
86
+ def do_ingestionpolicy_add_to
87
+ puts format("Added '%<policy>s' to '%<id>s'.",
88
+ id: options[:'<id>'],
89
+ policy: options[:'<policy>'])
90
+ end
91
+
92
+ def do_ingestionpolicy_remove_from
93
+ puts format("Removed '%<policy>s' from '%<id>s'.",
94
+ id: options[:'<id>'],
95
+ policy: options[:'<policy>'])
96
+ end
97
+
98
+ def do_ingestionpolicy
99
+ policy = data.fetch(:ingestionPolicyId, [])
100
+
101
+ if policy.empty?
102
+ puts "'#{options[:'<id>']}' has no ingestion policy."
103
+ else
104
+ puts policy
105
+ end
106
+ end
107
+
108
+ def do_invite_user
109
+ puts format("Sent invitation to '%<id>s'.", id: options[:'<id>'])
110
+ end
111
+
112
+ private
113
+
114
+ def filter_user_list
115
+ if options[:user]
116
+ data.delete_if { |a| a[:identifier].start_with?('sa::') }
117
+ elsif options[:service]
118
+ data.delete_if { |a| !a[:identifier].start_with?('sa::') }
119
+ end
120
+ end
121
+ end
122
+ end
@@ -74,5 +74,13 @@ module WavefrontDisplay
74
74
  def do_version
75
75
  puts data.max
76
76
  end
77
+
78
+ def do_affected_hosts
79
+ if data == [nil]
80
+ puts 'Alert event is not attached to any hosts.'
81
+ else
82
+ long_output
83
+ end
84
+ end
77
85
  end
78
86
  end
@@ -112,7 +112,7 @@ module WavefrontDisplay
112
112
  # long listing objects. Subclasses may define their own.
113
113
  #
114
114
  def priority_keys
115
- %i[id name]
115
+ %i[id name identifier]
116
116
  end
117
117
 
118
118
  def prioritize_keys(data, keys)
@@ -8,11 +8,12 @@ module WavefrontDisplay
8
8
  #
9
9
  class CloudIntegration < Base
10
10
  def do_list_brief
11
- multicolumn(:id, :service)
11
+ multicolumn(:id, :service, :name)
12
12
  end
13
13
 
14
14
  def do_describe
15
- readable_time(:lastReceivedDataPointMs, :lastProcessingTimestamp)
15
+ readable_time(:lastReceivedDataPointMs, :lastProcessingTimestamp,
16
+ :createdEpochMillis, :updatedEpochMillis)
16
17
  drop_fields(:forceSave, :inTrash, :deleted)
17
18
  long_output
18
19
  end
@@ -24,5 +25,17 @@ module WavefrontDisplay
24
25
  def do_disable
25
26
  puts "Disabled '#{options[:'<id>']}'."
26
27
  end
28
+
29
+ def do_awsid_generate
30
+ puts data
31
+ end
32
+
33
+ def do_awsid_delete
34
+ puts "Deleted external ID '#{options[:'<external_id>']}'."
35
+ end
36
+
37
+ def do_awsid_confirm
38
+ puts "'#{data}' is a registered external ID."
39
+ end
27
40
  end
28
41
  end
@@ -8,6 +8,7 @@ module WavefrontDisplayPrinter
8
8
  #
9
9
  class Long
10
10
  attr_reader :opts, :list, :kw
11
+
11
12
  #
12
13
  # @param data [Hash] of data to display
13
14
  # @param fields [Array[Symbol]] requred fields
@@ -55,7 +56,7 @@ module WavefrontDisplayPrinter
55
56
  def preened_value(value)
56
57
  return value unless value.is_a?(String) && value =~ /<.*>/
57
58
 
58
- value.gsub(%r{<\/?[^>]*>}, '').delete("\n")
59
+ value.gsub(%r{</?[^>]*>}, '').delete("\n")
59
60
  end
60
61
 
61
62
  # A recursive function which takes a structure, most likely a