├── .editorconfig
├── .gitattributes
├── .github
├── issue_template.md
└── pull_request_template.md
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── appveyor.yml
├── bin
├── idoitcli
├── idoitcli-dbg
└── idoitcli.php
├── composer.json
├── config
├── applications.json
├── dcim.json
├── default.json
├── location.json
├── networks.json
├── office.json
├── schema.json
└── template.json
├── idoit.bash-completion
├── phpunit.xml
└── src
├── Command
├── Cache.php
├── Call.php
├── Categories.php
├── Command.php
├── Create.php
├── Dummy.php
├── FixIP.php
├── Init.php
├── Log.php
├── Logs.php
├── Network.php
├── NextIP.php
├── Rack.php
├── Random.php
├── Read.php
├── Save.php
├── Search.php
├── Show.php
├── Status.php
└── Types.php
└── Service
├── Cache.php
├── HandleAttribute.php
├── IdoitAPI.php
├── IdoitAPIFactory.php
├── IdoitStatus.php
├── PrintData.php
├── Service.php
└── Validate.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | insert_final_newline = true
8 | charset = utf-8
9 | indent_style = space
10 | indent_size = 4
11 |
12 | [*.yml]
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.php diff=php
3 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | ### Expected behavior
9 |
10 |
11 |
12 | ### Actual behavior
13 |
14 |
15 |
16 | ### Steps to reproduce the behavior
17 |
18 |
19 |
20 | ### Environment
21 |
22 | | Question | Answer |
23 | | ------------------------- | ----------------- |
24 | | bheisig/idoitapi version | x.y.z |
25 | | i-doit version | x.y.z open/pro |
26 | | i-doit API add-on version | x.y.z |
27 |
28 | ### Logs and other useful output
29 |
30 |
41 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
4 |
5 | Short description
6 |
7 | Long description
8 |
9 | ### Added
10 |
11 | - First
12 | - Second
13 | - And so on…
14 |
15 | ### Changed
16 |
17 | - First
18 | - Second
19 | - And so on…
20 |
21 | ### Fixed
22 |
23 | - First
24 | - Second
25 | - And so on…
26 |
27 | ### Removed
28 |
29 | - First
30 | - Second
31 | - And so on…
32 |
33 | ### Confirmation
34 |
35 | By sending this pull request I accept the following conditions:
36 |
37 | - [ ] I have read the code of conduct and accept it.
38 | - [ ] I have read the contributing guidelines and accept them.
39 | - [ ] I accept that my contribution will be licensed under the AGPLv3.
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | vendor/
3 | *.tar.gz
4 | *.deb
5 | composer.lock
6 | README
7 | /idoit
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os:
2 | - linux
3 |
4 | language: php
5 |
6 | php:
7 | - 7.1
8 | - 7.2
9 | - 7.3
10 | - 7.4snapshot
11 |
12 | install:
13 | - travis_retry composer self-update
14 | - travis_retry composer install --prefer-source --no-interaction
15 |
16 | script:
17 | - composer ci
18 |
19 | notifications:
20 | email:
21 | on_success: never
22 | on_failure: always
23 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased][]
9 |
10 | _tbd_
11 |
12 | ## [0.9][] – 2020-01-08
13 |
14 | ### Added
15 |
16 | - `network`: Add new command to print list of IPv4 addresses
17 | - `rack`: Add new command to visualize hardware racks
18 |
19 | ### Changed
20 |
21 | - `save`: Handle more attributes as text fields
22 |
23 | ### Fixed
24 |
25 | - `save`: Do not ignore capacity (float) incl. unit (b, kb, mb, gb, tb)
26 | - `read`: Print object types when no arguments are given
27 |
28 | ## [0.8][] – 2019-07-31
29 |
30 | ☀️☀️☀️ Happy summer time ☀️☀️☀️
31 |
32 | ## Changed
33 |
34 | - Require PHP >= 7.1 (PHP 7.0 is end-of-life); recommend PHP 7.3
35 | - i-doit 1.13 or higher is required
36 | - i-doit API add-on 1.10.3 is required
37 | - Require PHP extension `zlib` instead of `bz2`
38 |
39 | ## Fixed
40 |
41 | - Allow API calls without credentials
42 | - `save`/`show`: Prevent PHP fatal error when encoding empty dialog+ attribute
43 | - `show`: Prevent any unwanted user interaction (`--yes`) if object not found or selection ambiguous
44 | - Make CLI option `-y`/`--yes` work after it was completely ignored
45 | - `read`: Convert values to strings before printing them which prevents PHP fatal errors
46 |
47 | ## [0.7][] – 2018-12-18
48 |
49 | This release is a great step forward with new features and tons of improvements. Happy holidays!
50 |
51 | ### Added
52 |
53 | - `save`: Create/update CMDB objects and their category entries
54 | - `log`: Add entry to i-doit logbook
55 | - `logs`: Print entries from i-doit logbook
56 | - `cache`: Create cache files needed for faster processing
57 | - Add support for custom categories with user-defined attributes
58 |
59 | ### Changed
60 |
61 | - `init`: Just create configuration file and do not cache files anymore
62 | - `help`/`-h`/`--help`: Print more information about using each command
63 | - Ignore categories which have no proper attributes
64 |
65 | ### Fixed
66 |
67 | - `init`: Ask to enable proxy settings
68 |
69 | ## [0.6][]
70 |
71 | ### Added
72 |
73 | - Support for Windows operating systems
74 | - `random`: Create persons with random names, e-mail addresses and desks
75 | - `random`: Install applications on laptops with license keys
76 |
77 | ### Fixed
78 |
79 | - `init`: Validation error occurs even before initialization could start
80 | - `init`: Ask for hostname if HTTP proxy will be used
81 |
82 | ## [0.5][] – 2018-04-24
83 |
84 | **Important notes:**
85 |
86 | 1. PHP 5.6 support is dropped. You need PHP 7.0 or higher.
87 | 2. Re-load cache with `idoitcli init`
88 |
89 | ### Added
90 |
91 | - `create`: Create new object or category entry
92 |
93 | ### Changed
94 |
95 | - Renamed binary file to `idoitcli`
96 | - Separate CLI-related code from application code
97 | - Drop PHP 5.6 support and require PHP 7.0 or higher
98 |
99 | ### Fixed
100 |
101 | - `show`: Not all category entries are printed
102 | - `init`: Misleading cache file names for categories
103 |
104 | ## [0.4][] – 2017-09-21
105 |
106 | ### Added
107 |
108 | - `read`: Allow wildcards (`*`) in object titles
109 | - `categories`: Print a list of available categories
110 | - `types`: Print a list of available object types and group them
111 | - `fixip`: Assign IPs to subnets
112 | - Display error when user asks for specific category entries but category is not assigned to object type(s)
113 |
114 | ## [0.3][] – 2017-07-25
115 |
116 | ### Added
117 |
118 | - `show`: Show everything about an object
119 | - `read`: Read object by its identifier
120 | - `call`: Perform self-defined API requests
121 | - Find errors in additional configuration files
122 |
123 | ### Fixed
124 |
125 | - Show the right command description (`idoit help [command]` or `idoit [command] --help`)
126 | - Command `idoit read` was unable to fetch a list of objects or attributes for an object when configuration setting `limitBatchRequest` is disabled (`0`).
127 |
128 | ## [0.2][] – 2017-04-06
129 |
130 | ### Added
131 |
132 | - Find your data with new command `search`
133 | - List attributes and assigned categories with command `read`
134 | - Sort results with command `read`
135 | - Strip HTML code in results for command `read`
136 | - Limit batch requests (if configured) for command `read` with setting `limitBatchRequests`
137 | - Describe a lot of examples for command `read` in [documentation](README.md) und built-in help
138 | - Simple bash completion
139 | - Create racks and servers with categories "formfactor", "cpu", "model", and "location" with command `random`
140 | - Put servers into empty racks with command `random`
141 | - More examples in `docs/` for command `random`
142 | - Show message if cache is out-dated, see configuration setting `cacheLifetime`
143 |
144 | ### Fixed
145 |
146 | - Errors in built-in `help`
147 | - Removed out-dated command line options
148 | - Source code documentation
149 |
150 | ## 0.1 – 2017-02-06
151 |
152 | Initial release
153 |
154 | [Unreleased]: https://github.com/bheisig/i-doit-cli/compare/0.9...HEAD
155 | [0.9]: https://github.com/bheisig/i-doit-cli/compare/0.8...0.9
156 | [0.8]: https://github.com/bheisig/i-doit-cli/compare/0.7...0.8
157 | [0.7]: https://github.com/bheisig/i-doit-cli/compare/0.6...0.7
158 | [0.6]: https://github.com/bheisig/i-doit-cli/compare/0.5...0.6
159 | [0.5]: https://github.com/bheisig/i-doit-cli/compare/0.4...0.5
160 | [0.4]: https://github.com/bheisig/i-doit-cli/compare/0.3...0.4
161 | [0.3]: https://github.com/bheisig/i-doit-cli/compare/0.2...0.3
162 | [0.2]: https://github.com/bheisig/i-doit-cli/compare/0.1...0.2
163 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | - Using welcoming and inclusive language
12 | - Being respectful of differing viewpoints and experiences
13 | - Gracefully accepting constructive criticism
14 | - Focusing on what is best for the community
15 | - Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | - Trolling, insulting/derogatory comments, and personal or political attacks
21 | - Public or private harassment
22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | - Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by [contacting the project team][mail]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at
44 |
45 | [homepage]: https://www.contributor-covenant.org
46 | [mail]: mailto:benjamin@heisig.name
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributors welcome!
2 |
3 | Thank you very much for your interest in this project! There are plenty of ways you can support us. :-)
4 |
5 | ## Code of Conduct
6 |
7 | We like you to read and follow our [code of conduct](CODE_OF_CONDUCT.md) before contributing. Thank you.
8 |
9 | ## Use it
10 |
11 | The best and (probably) easiest way to help is to use this library in your own projects. It would be very nice to share your thoughts with us. We love to hear from you.
12 |
13 | If you have questions how to use it properly read the [documentation](README.md) carefully.
14 |
15 | ## Report bugs
16 |
17 | If you find something strange please report it to [our issue tracker][issues].
18 |
19 | ## Make a wish
20 |
21 | Of course, there are some features in the pipeline. However, if you have good ideas how to improve this application please let us know! Write a feature request [in our issue tracker][issues].
22 |
23 | ## Setup a development environment
24 |
25 | If you like to contribute source code, documentation snippets, self-explaining examples or other useful bits, fork this repository, setup the environment and make a pull request.
26 |
27 | ~~~ {.bash}
28 | git clone https://github.com/bheisig/i-doit-cli.git
29 | ~~~
30 |
31 | If you have a GitHub account create a fork first and then clone the repository.
32 |
33 | After that, change to your cloned repository and setup the environment with Composer:
34 |
35 | ~~~ {.bash}
36 | cd i-doit-cli/
37 | composer install
38 | ~~~
39 |
40 | Now it is the time to do your stuff. Do not forget to commit your changes. When you are done consider to make a pull requests.
41 |
42 | Notice, that any of your contributions merged into this repository will be [licensed under the AGPLv3](LICENSE).
43 |
44 | ## Requirements
45 |
46 | Developers must meet these requirements:
47 |
48 | - See requirements mentioned in the [documentation](README.md)
49 | - [Composer](https://getcomposer.org/)
50 | - [Git](https://git-scm.com/)
51 |
52 | ## Run it!
53 |
54 | To run `idoitcli` you do not need to build a binary first. Just run:
55 |
56 | ~~~ {.bash}
57 | bin/idoitcli
58 | ~~~
59 |
60 | ## Add your own command
61 |
62 | Each command has its own PHP class located under `src/Command`. There is a dummy class you can use as a skeleton. Copy it:
63 |
64 | ~~~ {.bash}
65 | cd src/Command/
66 | cp Dummy.php MyCommand.php
67 | ~~~
68 |
69 | Note, that the file name must be in camel-case and it ends with `.php`.
70 |
71 | Edit this file with your favorite editor. The class name must be the same as the file name (without the file extension). The entry point is the public method `execute()`.
72 |
73 | Next step is to register your new command. This is done inside `bin/idoitcli.php`. Add something like this:
74 |
75 | ~~~ {.php}
76 | $app
77 | ->addCommand(
78 | 'my-command',
79 | __NAMESPACE__ . '\\Command\\MyCommand',
80 | 'Dummy command to print "Hello, World!"'
81 | );
82 | ~~~
83 |
84 | Now you are able to execute your command:
85 |
86 | ~~~ {.bash}
87 | bin/idoitcli my-command
88 | ~~~
89 |
90 | Further steps:
91 |
92 | - Add options to your command with `$app->addOption()`
93 | - Overwrite public method `showUsage()` to print usage with option `--help`
94 |
95 | ## Release new version
96 |
97 | …to the public. You need commit rights for this repository.
98 |
99 | 1. Bump version: `composer config extra.version `
100 | 3. Keep [`CHANGELOG.md`](CHANGELOG.md) up-to-date
101 | 4. Commit changes: `git commit -a -m "Bump version to $(composer config extra.version)"`
102 | 5. Perform some tests, for example `composer ci`
103 | 6. Build binary file: `composer build`
104 | 7. Create distribution tarball: `composer dist`
105 | 8. Build PHIVE files: `composer phive`
106 | 9. Create Git tag: `git tag -s -a -m "Release version $(composer config extra.version)" $(composer config extra.version)`
107 | 10. Push changes: `git push --follow-tags`
108 | 11. Create new release on GitHub based on the last tag
109 | 12. Upload these files and add them to the release:
110 | - Distribution tarball: `idoitcli-.tar.gz`
111 | - Binaray file: `idoitcli`
112 | - PHIVE files: `idoitcli.phar`, `idoitcli.phar.asc`
113 | 13. Cleanup project directory: `composer clean`
114 |
115 | If any step produces an error please think twice before releasing. ;-)
116 |
117 | ## Create OS-related packages
118 |
119 | At the moment, only Debian GNU/Linux is supported:
120 |
121 | 1. Make sure you have [`fpm`](https://github.com/jordansissel/fpm) installed
122 | 2. Build binary file: `composer build`
123 | 3. Create .deb package file: `composer deb`
124 |
125 | This results in a file `idoitcli__all.deb`.
126 |
127 | ## Composer scripts
128 |
129 | This project comes with some useful composer scripts:
130 |
131 | | Command | Description |
132 | | ----------------------------- | --------------------------------------------------------- |
133 | | `composer ci` | Perform continuous integration tasks |
134 | | `composer clean` | Cleanup project directory |
135 | | `composer build` | Create a binary |
136 | | `composer deb` | Create a Debian GNU/Linux package |
137 | | `composer dist` | Create a distribution tarball |
138 | | `composer find-forbidden` | Find forbidden words in source code |
139 | | `composer gitstats` | Create Git statistics |
140 | | `composer gource` | Visualize Git history |
141 | | `composer is-built` | Test whether binary is already built |
142 | | `composer lint` | Perform all lint checks |
143 | | `composer lint-php` | Check syntax of PHP files |
144 | | `composer lint-json` | Check syntax of JSON files |
145 | | `composer lint-xml` | Check syntax of XML files |
146 | | `composer lint-yaml` | Check syntax of YAML files |
147 | | `composer phive` | Build PHIVE files |
148 | | `composer phpcompatibility` | Run PHP compatibility checks |
149 | | `composer phpcpd` | Detect copy/paste in source code |
150 | | `composer phpcs` | Detect violations of defined coding standards |
151 | | `composer phploc` | Print source code statistics |
152 | | `composer phpmd` | Detect mess in source code |
153 | | `composer phpmnd` | Detect magic numbers in source code |
154 | | `composer phpstan` | Analyze source code |
155 | | `composer security-checker` | Look for dependencies with known security vulnerabilities |
156 | | `composer system-check` | Run some system checks |
157 | | `composer test` | Perform some tests with the built binary |
158 |
159 | For example, execute `composer ci`.
160 |
161 | ## Donate
162 |
163 | Last but not least, if you think this project is useful for your daily work, consider a donation. What about a beer?
164 |
165 | ## Further reading
166 |
167 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
168 |
169 | [issues]: https://github.com/bheisig/i-doit-cli/issues
170 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # i-doit CLI tool
2 |
3 | Access your CMDB on the command line interface
4 |
5 | [](https://packagist.org/packages/bheisig/idoitcli)
6 | [](https://php.net/)
7 | [](https://travis-ci.org/bheisig/i-doit-cli)
8 |
9 | ## About
10 |
11 | [i-doit](https://i-doit.com) is a software application for IT documentation and a CMDB (Configuration Management Database). This application is very useful to collect all your knowledge about the IT infrastructure you are dealing with. i-doit is a Web application and [has an exhausting API](https://kb.i-doit.com/pages/viewpage.action?pageId=37355644) which is very useful to automate your infrastructure.
12 |
13 | This application provides a simple, but powerful command line interface to access your CMDB data stored in i-doit.
14 |
15 | ## Features
16 |
17 | * Read information about objects their types and even their attributes
18 | * Find your needle in the haystack called CMDB
19 | * Do you need a free IP address in a particular subnet? This app suggests one for you.
20 | * Stress your system: auto-generate thousands of objects
21 |
22 | ## Requirements
23 |
24 | Before using this app your system must meet the following requirements:
25 |
26 | * Of course, i-doit pro/open, version 1.13 or higher
27 | * i-doit API add-on, version 1.10.3 or higher
28 | * Any POSIX operating system (GNU/Linux, *BSD, MacOS) or Windows
29 | * PHP >= 7.1 (7.3 is recommended)
30 | * PHP extensions `calendar`, `cli`, `cURL`, `json`, `phar` and `zlib`
31 | * PHP extension `pcntl` is optional, but highly recommended (non-Windows only)
32 |
33 | ## Install and update
34 |
35 | You have two good options to download and install this application:
36 |
37 | * Download any stable release (**recommended**)
38 | * Use [PHIVE](https://phar.io/)
39 |
40 | ### Download release
41 |
42 | Download the latest stable version of the binary `idoitcli` [from the release site](https://github.com/bheisig/i-doit-cli/releases). Then install it system-wide:
43 |
44 | ~~~ {.bash}
45 | curl -OL https://github.com/bheisig/i-doit-cli/releases/download/0.7/idoitcli
46 | chmod 755 idoitcli
47 | sudo mv idoitcli /usr/local/bin/
48 | ~~~
49 |
50 | To be up-to-date just repeat the steps above.
51 |
52 | ### Use PHIVE
53 |
54 | With [PHIVE](https://phar.io/) you are able to download and install PHAR files on your system. Additionally, it will verify the SHA1 and GPG signatures which is highly recommended. If you have PHIVE already installed you can fetch the latest version of this application:
55 |
56 | ~~~ {.bash}
57 | sudo phive install --global bheisig/i-doit-cli
58 | ~~~
59 |
60 | This will install an executable binary to `/usr/bin/idoitcli`.
61 |
62 | If a new release is available you can perform an update:
63 |
64 | ~~~ {.bash}
65 | sudo phive update --global
66 | ~~~
67 |
68 | ## Usage
69 |
70 | Just run the application to show the basic usage:
71 |
72 | ~~~ {.bash}
73 | idoitcli
74 | ~~~
75 |
76 | ## First steps
77 |
78 | This application caches a lot locally to give you the best user experience. Run the `init` command:
79 |
80 | ~~~ {.bash}
81 | idoitcli init
82 | ~~~
83 |
84 | Some simple questions will be asked how to access your i-doit installation. Next step is to create cache files:
85 |
86 | ~~~ {.bash}
87 | idoitcli cache
88 | ~~~
89 |
90 | After that some files will be created in your home directory under `~/.idoitcli/`: Each i-doit installation has its own cache files under `data/`. Your user-defined configuration file is called `config.json`.
91 |
92 | You may check your current status by running:
93 |
94 | ~~~ {.bash}
95 | idoitcli status
96 | ~~~
97 |
98 | This gives you some basic information about your i-doit installation, your settings and your user.
99 |
100 | ## Access your CMDB data
101 |
102 | This is probably the best part: Read information about objects, their types and even attributes.
103 |
104 | List all object types:
105 |
106 | ~~~ {.bash}
107 | idoitcli read
108 | idoitcli read /
109 | ~~~
110 |
111 | List server objects:
112 |
113 | ~~~ {.bash}
114 | idoitcli read server/
115 | idoitcli read server/host.example.net
116 | idoitcli read server/*.example.net
117 | idoitcli read server/host.*.net
118 | idoitcli read server/*.*.net
119 | idoitcli read server/host*
120 | idoitcli read server/*
121 | ~~~
122 |
123 | Show common information about server "host.example.net":
124 |
125 | ~~~ {.bash}
126 | idoitcli read server/host.example.net
127 | ~~~
128 |
129 | Show common information about object identifier "42":
130 |
131 | ~~~ {.bash}
132 | idoitcli read 42
133 | ~~~
134 |
135 | Show common information about one or more objects by their titles:
136 |
137 | ~~~ {.bash}
138 | idoitcli read *.example.net
139 | idoitcli read host.*.net
140 | idoitcli read *.*.net
141 | idoitcli read host*
142 | ~~~
143 |
144 | List assigned categories:
145 |
146 | ~~~ {.bash}
147 | idoitcli read server/host.example.net/
148 | ~~~
149 |
150 | Show values from category "model" for this server:
151 |
152 | ~~~ {.bash}
153 | idoitcli read server/host.example.net/model
154 | ~~~
155 |
156 | Show values from category "model" for one or more servers:
157 |
158 | ~~~ {.bash}
159 | idoitcli read server/*.example.net/model
160 | idoitcli read server/host.*.net/model
161 | idoitcli read server/*.*.net/model
162 | idoitcli read server/host*/model
163 | idoitcli read server/*/model
164 | ~~~
165 |
166 | Or just show the name of the model:
167 |
168 | ~~~ {.bash}
169 | idoitcli read server/host.example.net/model/model
170 | ~~~
171 |
172 | List available attributes for category "model":
173 |
174 | ~~~ {.bash}
175 | idoitcli read server/host.example.net/model/
176 | ~~~
177 |
178 | You may leave the object type empty for specific objects, for example:
179 |
180 | ~~~ {.bash}
181 | idoitcli read host.example.net/model
182 | ~~~
183 |
184 | **Notice:** These examples work great with unique names. That is why it is common practice to give objects unique titles that are not in conflict with object types and categories.
185 |
186 | ## Show everything about an object
187 |
188 | ~~~ {.bash}
189 | idoitcli show myserver
190 | idoitcli show "My Server"
191 | idoitcli show 42
192 | ~~~
193 |
194 | ## Find your data
195 |
196 | Find your needle in the haystack called CMDB:
197 |
198 | ~~~ {.bash}
199 | idoitcli search myserver
200 | idoitcli search "My Server"
201 | ~~~
202 |
203 | ## Show the next free IPv4 address
204 |
205 | Get the next free IPv4 address for a particular subnet:
206 |
207 | ~~~ {.bash}
208 | idoitcli nextip SUBNET
209 | ~~~
210 |
211 | `SUBNET` may be the object title or its identifier.
212 |
213 | ## Auto-generate objects
214 |
215 | For testing purposes stress your i-doit installation and let the app create thousands of objects, attributes and relations between objects:
216 |
217 | ~~~ {.bash}
218 | idoitcli -c FILE random
219 | ~~~
220 |
221 | There are some examples located under `docs/`.
222 |
223 | ## Update the caches
224 |
225 | If your CMDB configuration has changed you need to re-create the cache files by running the `cache` command:
226 |
227 | ~~~ {.bash}
228 | idoitcli cache
229 | ~~~
230 |
231 | ## Playground
232 |
233 | Perform self-defined API requests – pass request as argument:
234 |
235 | ~~~ {.bash}
236 | idoitcli call '{"version": "2.0","method": "idoit.version","params": {"apikey": "c1ia5q","language": "en"},"id": 1}'
237 | ~~~
238 |
239 | Pipe request:
240 |
241 | ~~~ {.bash}
242 | echo '{"version": "2.0","method": "idoit.version","params": {"apikey": "c1ia5q","language": "en"},"id": 1}' | idoitcli call
243 | ~~~
244 |
245 | Read request from file:
246 |
247 | ~~~ {.bash}
248 | cat request.txt | idoitcli call
249 | ~~~
250 |
251 | Read request from standard input (double-enter to execute):
252 |
253 | ~~~ {.bash}
254 | idoitcli call
255 | ~~~
256 |
257 | ## Configuration
258 |
259 | There are some ways to set your configurations settings:
260 |
261 | 1. System-wide settings are stored under `/etc/idoitcli/config.json`.
262 | 2. User-defined settings are stored under `~/.idoitcli/config.json`.
263 | 3. Pass your run-time settings by using the options `-c` or `--config`.
264 |
265 | Keep in mind this order matters. Each file is optional. Any combination is possible. Furthermore, to include even more configuration files you can pass the options `-c` and `--config` as often as you like.
266 |
267 | The configuration files are JSON-formatted.
268 |
269 | ## Contribute
270 |
271 | Please, report any issues to [our issue tracker](https://github.com/bheisig/i-doit-cli/issues). Pull requests and OS distribution packages are very welcomed. For further information, see file [`CONTRIBUTING.md`](CONTRIBUTING.md).
272 |
273 | ## Copyright & License
274 |
275 | Copyright (C) 2016-19 [Benjamin Heisig](https://benjamin.heisig.name/)
276 |
277 | Licensed under the [GNU Affero GPL version 3 or later (AGPLv3+)](https://gnu.org/licenses/agpl.html). This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
278 |
279 | [List of first names and gender:](https://www.heise.de/ct/ftp/07/17/182/) Copyright (C) 2007-2008 Jörg Michael, licensed under [GNU Free Documentation License](https://www.gnu.org/licenses/fdl.html)
280 |
281 | [List of surnames:](https://github.com/HBehrens/phonet4n/blob/master/src/Tests/data/nachnamen.txt) Copyright (C) Heiko Behrens, lisenced under [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0)
282 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | Thank you for taking time reading the following information carefully. Your [contribution](CONTRIBUTING.md) is highly welcomed!
4 |
5 | ## Supported versions
6 |
7 | Only the **latest stable release** is supported with security updates.
8 |
9 | ## Reporting a vulnerability
10 |
11 | I highly encourage you to follow the principles of a **[responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure)**:
12 |
13 | - Do not publish your research before contacting me first.
14 | - Write me an e-mail:
15 | - Sign and encrypt your e-mail with OpenPGP/GnuPG.
16 | - Do not open a public issue until my confirmation.
17 | - If the vulnerability is accepted please give me at least 4 weeks to release proper security updates.
18 | - After the security updates are released feel free to publish your research. A CVE is highly appreciated.
19 |
20 | **Note:** This is a private side-project, maintained in my spare time. Therefore, I can't promise to answer you within three days but I try to respond within 3-5 days.
21 |
22 | Thank you.
23 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | build: false
2 |
3 | platform:
4 | - x64
5 |
6 | clone_depth: 5
7 |
8 | environment:
9 | matrix:
10 | - PHP_VERSION: '7.1'
11 | XDEBUG_VERSION: '2.6.1-7.1-vc14-nts-x86_64'
12 | - PHP_VERSION: '7.2'
13 | XDEBUG_VERSION: '2.6.1-7.2-vc15-nts-x86_64'
14 | - PHP_VERSION: '7.3'
15 | XDEBUG_VERSION: '2.7.0-7.3-vc15-nts-x86_64'
16 |
17 | cache:
18 | - '%LOCALAPPDATA%\Composer\files -> composer.lock'
19 | - composer.phar
20 | - C:\ProgramData\chocolatey\bin -> appveyor.yml
21 | - C:\ProgramData\chocolatey\lib -> appveyor.yml
22 | - c:\tools\php -> appveyor.yml
23 |
24 | init:
25 | - SET PATH=C:\Program Files\OpenSSL;c:\tools\php;%PATH%
26 | - SET COMPOSER_NO_INTERACTION=1
27 | - SET PHP=1
28 | - SET ANSICON=121x90 (121x90)
29 |
30 | install:
31 | - IF EXIST c:\tools\php (SET PHP=0)
32 | - ps: appveyor-retry cinst --params '""/InstallDir:C:\tools\php""' --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $env:PHP_VERSION | sort { [version]($_ -split '\|' | select -last 1) } -Descending | Select-Object -first 1) -replace '[php|]','')
33 | - cd c:\tools\php
34 | - IF %PHP%==1 copy php.ini-production php.ini /Y
35 | - IF %PHP%==1 echo date.timezone="UTC" >> php.ini
36 | - IF %PHP%==1 echo extension_dir=ext >> php.ini
37 | - IF %PHP%==1 echo extension=php_bz2.dll >> php.ini
38 | - IF %PHP%==1 echo extension=php_curl.dll >> php.ini
39 | - IF %PHP%==1 echo extension=php_fileinfo.dll >> php.ini
40 | - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini
41 | - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
42 | - IF %PHP%==1 echo extension=php_xsl.dll >> php.ini
43 | - IF %PHP%==1 curl -fsS -o c:\tools\php\ext\php_xdebug-%XDEBUG_VERSION%.dll https://xdebug.org/files/php_xdebug-%XDEBUG_VERSION%.dll
44 | - IF %PHP%==1 echo zend_extension=php_xdebug-%XDEBUG_VERSION%.dll >> php.ini
45 | - IF %PHP%==1 curl -fsS -o c:\tools\php\extras\ssl\cacert.pem https://curl.haxx.se/ca/cacert.pem
46 | - IF %PHP%==1 echo curl.cainfo="C:\tools\php\extras\ssl\cacert.pem" >> php.ini
47 | - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat
48 | - appveyor-retry appveyor DownloadFile https://getcomposer.org/composer.phar
49 | - cd %APPVEYOR_BUILD_FOLDER%
50 | - appveyor-retry composer install --no-progress --profile
51 | - appveyor-retry composer self-update --update-keys
52 |
53 | test_script:
54 | - cd %APPVEYOR_BUILD_FOLDER%
55 | - ps: composer win-ci
56 | - php idoitcli --help
57 | - php idoitcli --version
58 |
59 | notifications:
60 | - provider: Email
61 | on_build_success: false
62 | on_build_failure: true
63 | on_build_status_changed: true
64 |
--------------------------------------------------------------------------------
/bin/idoitcli:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | .
19 | *
20 | * @author Benjamin Heisig
21 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
22 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
23 | * @link https://github.com/bheisig/i-doit-cli
24 | */
25 |
26 | require_once('idoitcli.php');
27 |
--------------------------------------------------------------------------------
/bin/idoitcli-dbg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | export XDEBUG_CONFIG="idekey=idoitcli remote_enable=1 remote_host=localhost profiler_enable=0 remote_connect_back=0 remote_mode=req"
4 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
5 | /usr/bin/env php "$DIR"/idoitcli.php "$@"
6 |
--------------------------------------------------------------------------------
/bin/idoitcli.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli;
28 |
29 | use bheisig\idoitcli\Command\Network;
30 | use bheisig\idoitcli\Command\Rack;
31 | use \Exception;
32 | use bheisig\cli\App;
33 | use bheisig\cli\IO;
34 |
35 | try {
36 | require_once __DIR__ . '/../vendor/autoload.php';
37 |
38 | (new App())
39 | ->addConfigSettings([
40 | 'appDir' => __DIR__ . '/..',
41 | 'baseDir' => (strtolower(substr(PHP_OS, 0, 3)) === 'win') ?
42 | $_SERVER['LOCALAPPDATA'] . '\\idoitcli' :
43 | $_SERVER['HOME'] . '/.idoitcli',
44 | 'dataDir' => (strtolower(substr(PHP_OS, 0, 3)) === 'win') ?
45 | $_SERVER['LOCALAPPDATA'] . '\\idoitcli\\data' :
46 | $_SERVER['HOME'] . '/.idoitcli/data'
47 | ])
48 | ->addCommand(
49 | 'cache',
50 | __NAMESPACE__ . '\\Command\\Cache',
51 | 'Create cache files needed for faster processing'
52 | )
53 | ->addCommand(
54 | 'call',
55 | __NAMESPACE__ . '\\Command\\Call',
56 | 'Perform self-defined API requests'
57 | )
58 | ->addCommand(
59 | 'categories',
60 | __NAMESPACE__ . '\\Command\\Categories',
61 | 'Print a list of available categories'
62 | )
63 | ->addCommand(
64 | 'create',
65 | __NAMESPACE__ . '\\Command\\Create',
66 | 'Create new object or category entry'
67 | )
68 | ->addCommand(
69 | 'fixip',
70 | __NAMESPACE__ . '\\Command\\FixIP',
71 | 'Assign IPs to subnets'
72 | )
73 | // Extent basic command:
74 | ->addCommand(
75 | 'init',
76 | __NAMESPACE__ . '\\Command\\Init',
77 | 'Create/update user-defined or system-wide configuration settings'
78 | )
79 | ->addCommand(
80 | 'log',
81 | __NAMESPACE__ . '\\Command\\Log',
82 | 'Add entry to i-doit logbook'
83 | )
84 | ->addCommand(
85 | 'logs',
86 | __NAMESPACE__ . '\\Command\\Logs',
87 | 'Print entries from i-doit logbook'
88 | )
89 | ->addCommand(
90 | 'network',
91 | __NAMESPACE__ . '\\Command\\Network',
92 | 'Print list of IPv4 addresses'
93 | )
94 | ->addCommand(
95 | 'nextip',
96 | __NAMESPACE__ . '\\Command\\NextIP',
97 | 'Fetch the next free IPv4 address'
98 | )
99 | ->addCommand(
100 | 'rack',
101 | __NAMESPACE__ . '\\Command\\Rack',
102 | 'Visualize hardware rack'
103 | )
104 | ->addCommand(
105 | 'random',
106 | __NAMESPACE__ . '\\Command\\Random',
107 | 'Create randomized data'
108 | )
109 | ->addCommand(
110 | 'read',
111 | __NAMESPACE__ . '\\Command\\Read',
112 | 'Fetch information from your CMDB'
113 | )
114 | ->addCommand(
115 | 'save',
116 | __NAMESPACE__ . '\\Command\\Save',
117 | 'Create/update CMDB objects and their category entries'
118 | )
119 | ->addCommand(
120 | 'search',
121 | __NAMESPACE__ . '\\Command\\Search',
122 | 'Find your needle in the haystack called CMDB'
123 | )
124 | ->addCommand(
125 | 'show',
126 | __NAMESPACE__ . '\\Command\\Show',
127 | 'Show everything about an object'
128 | )
129 | ->addCommand(
130 | 'status',
131 | __NAMESPACE__ . '\\Command\\Status',
132 | 'Current status information'
133 | )
134 | ->addCommand(
135 | 'types',
136 | __NAMESPACE__ . '\\Command\\Types',
137 | 'Print a list of available object types and group them'
138 | )
139 | // Common options:
140 | ->addOption(
141 | 'y',
142 | 'yes',
143 | App::NO_VALUE
144 | )
145 | // Used by command "save":
146 | ->addOption(
147 | 'a',
148 | 'attribute',
149 | App::OPTION_NOT_REQUIRED
150 | )
151 | // Used by command "log":
152 | ->addOption(
153 | 'm',
154 | 'message',
155 | App::OPTION_NOT_REQUIRED
156 | )
157 | // Used by command "logs":
158 | ->addOption(
159 | 'f',
160 | 'follow',
161 | App::NO_VALUE
162 | )
163 | ->addOption(
164 | null,
165 | 'id',
166 | App::OPTION_NOT_REQUIRED
167 | )
168 | ->addOption(
169 | 'n',
170 | 'number',
171 | App::OPTION_NOT_REQUIRED
172 | )
173 | ->addOption(
174 | null,
175 | 'since',
176 | App::OPTION_NOT_REQUIRED
177 | )
178 | ->addOption(
179 | null,
180 | 'title',
181 | App::OPTION_NOT_REQUIRED
182 | )
183 | ->addOption(
184 | null,
185 | 'type',
186 | App::OPTION_NOT_REQUIRED
187 | )
188 | // Used by command "network":
189 | ->addOption(
190 | null,
191 | Network::OPTION_FREE,
192 | App::NO_VALUE
193 | )
194 | ->addOption(
195 | null,
196 | Network::OPTION_USED,
197 | App::NO_VALUE
198 | )
199 | // Used by command "rack":
200 | ->addOption(
201 | null,
202 | Rack::OPTION_FRONT,
203 | App::NO_VALUE
204 | )
205 | ->addOption(
206 | null,
207 | Rack::OPTION_BACK,
208 | App::NO_VALUE
209 | )
210 | ->addOption(
211 | null,
212 | Rack::OPTION_SKIP_EMPTY_UNITS,
213 | App::NO_VALUE
214 | )
215 | ->run();
216 | } catch (Exception $e) {
217 | IO::err($e->getMessage());
218 |
219 | exit(255);
220 | }
221 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bheisig/idoitcli",
3 | "description": "Access your CMDB on the command line interface",
4 | "type": "project",
5 | "keywords": ["i-doit", "cmdb", "it documentation", "cli", "api", "json-rpc"],
6 | "homepage": "https://github.com/bheisig/i-doit-cli",
7 | "license": "AGPL-3.0+",
8 | "authors": [
9 | {
10 | "name": "Benjamin Heisig",
11 | "email": "benjamin@heisig.name",
12 | "homepage": "https://benjamin.heisig.name/",
13 | "role": "Developer"
14 | }
15 | ],
16 | "support": {
17 | "issues": "https://github.com/bheisig/i-doit-cli/issues",
18 | "source": "https://github.com/bheisig/i-doit-cli"
19 | },
20 | "require": {
21 | "php": ">=7.1.0",
22 | "ext-Phar": "*",
23 | "ext-zlib": "*",
24 | "bheisig/cli": "@dev",
25 | "bheisig/idoitapi": "@dev"
26 | },
27 | "require-dev": {
28 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
29 | "j13k/yaml-lint": "^1.1",
30 | "jakub-onderka/php-parallel-lint": "^1.0",
31 | "macfja/phar-builder": "^0.2.6",
32 | "phpcompatibility/php-compatibility": "*",
33 | "phploc/phploc": "^4.0",
34 | "phpmd/phpmd" : "@stable",
35 | "phpstan/phpstan": "^0.11.6",
36 | "phpunit/phpunit": "^7",
37 | "povils/phpmnd": "^2",
38 | "roave/security-advisories": "dev-master",
39 | "sclable/xml-lint": "^0.2.4",
40 | "sebastian/phpcpd": "^4.1.0",
41 | "seld/jsonlint": "^1.7",
42 | "sensiolabs/security-checker": "^5",
43 | "sllh/composer-lint": "^1.0",
44 | "squizlabs/php_codesniffer": "*"
45 | },
46 | "suggest": {
47 | "ext-xdebug": "Needed for code coverage with phpunit"
48 | },
49 | "prefer-stable" : false,
50 | "autoload": {
51 | "psr-4": {
52 | "bheisig\\idoitcli\\": "src/"
53 | }
54 | },
55 | "autoload-dev": {
56 | "psr-4": {
57 | "bheisig\\idoitcli\\tests\\": "tests/"
58 | }
59 | },
60 | "bin": ["bin/idoitcli"],
61 | "config": {
62 | "process-timeout": 86400,
63 | "sllh-composer-lint": {
64 | "php": false,
65 | "type": true,
66 | "minimum-stability": true,
67 | "version-constraints": true
68 | },
69 | "sort-packages": true
70 | },
71 | "scripts": {
72 | "ci": [
73 | "@composer system-check",
74 | "@composer lint",
75 | "@composer find-forbidden",
76 | "@composer security-checker",
77 | "@composer phpcompatibility",
78 | "@composer phpcpd",
79 | "@composer phpcs",
80 | "@composer phpstan",
81 | "@composer build",
82 | "@composer test",
83 | "@composer dist",
84 | "@composer clean"
85 | ],
86 | "clean": [
87 | "rm -f `composer config extra.name`",
88 | "rm -rf *.tar.gz ./`composer config extra.name`-`composer config extra.version`/",
89 | "rm -f `composer config extra.name`.phar `composer config extra.name`.phar.asc"
90 | ],
91 | "build": [
92 | "@composer dump-autoload --classmap-authoritative",
93 | "php -d phar.readonly=0 ./vendor/bin/phar-builder package --quiet composer.json",
94 | "mv `composer config extra.name`.phar `composer config extra.name`",
95 | "chmod +x `composer config extra.name`",
96 | "@composer dump-autoload"
97 | ],
98 | "deb": [
99 | "@composer is-built",
100 | "touch .dummy",
101 | "rm -f `composer config extra.name`_`composer config extra.version`_all.deb",
102 | "fpm -s dir -t deb -n `composer config extra.name` -v `composer config extra.version` -a all --license \"`composer config license`\" -m \"Benjamin Heisig \" --vendor \"Benjamin Heisig \" --description \"`composer config description`\" --url \"`composer config homepage`\" -d php -d php-cli -d php-common -d php-bz2 -d php-curl -d php-json `composer config extra.name`=/usr/bin/`composer config extra.name` .dummy=/etc/`composer config extra.name`",
103 | "rm .dummy",
104 | "lintian `composer config extra.name`_`composer config extra.version`_all.deb"
105 | ],
106 | "dist": [
107 | "@composer is-built",
108 | "rm -rf ./`composer config extra.name`-`composer config extra.version`/",
109 | "mkdir ./`composer config extra.name`-`composer config extra.version`/",
110 | "cp -r `composer config extra.name` ./`composer config extra.name`-`composer config extra.version`/",
111 | "cp -r CHANGELOG.md ./`composer config extra.name`-`composer config extra.version`/",
112 | "cp -r CONTRIBUTING.md ./`composer config extra.name`-`composer config extra.version`/",
113 | "cp -r LICENSE ./`composer config extra.name`-`composer config extra.version`/",
114 | "cp -r README.md ./`composer config extra.name`-`composer config extra.version`/",
115 | "tar czf `composer config extra.name`-`composer config extra.version`.tar.gz ./`composer config extra.name`-`composer config extra.version`/",
116 | "rm -r ./`composer config extra.name`-`composer config extra.version`/"
117 | ],
118 | "find-forbidden": [
119 | "! grep -rEn \"(var_dump|die|exit)\\(\" src/*"
120 | ],
121 | "gitstats": "gitstats -c project_name=`composer config extra.name` . gitstats",
122 | "gource": "gource -1280x720 --seconds-per-day 3 --auto-skip-seconds 1 --title `composer config extra.name`",
123 | "is-built": "test -x `composer config extra.name` || ( echo 'Run \"composer build\" first' && exit 1 )",
124 | "lint": [
125 | "@composer lint-php",
126 | "@composer lint-json",
127 | "@composer lint-xml",
128 | "@composer lint-yaml"
129 | ],
130 | "lint-json": "./vendor/bin/jsonlint composer.json config/default.json config/schema.json config/template.json",
131 | "lint-php": "./vendor/bin/parallel-lint --exclude vendor --blame .",
132 | "lint-xml": "./vendor/bin/xmllint --recursive --exclude=vendor --skip-xsd --verbose .",
133 | "lint-yaml": [
134 | "./vendor/bin/yaml-lint .travis.yml",
135 | "./vendor/bin/yaml-lint appveyor.yml"
136 | ],
137 | "phive": [
138 | "@composer is-built",
139 | "cp `composer config extra.name` `composer config extra.name`.phar",
140 | "gpg --detach-sign --output `composer config extra.name`.phar.asc `composer config extra.name`.phar"
141 | ],
142 | "phpcompatibility": "./vendor/bin/phpcs -p --colors --extensions=php --standard=PHPCompatibility --runtime-set testVersion 7.1 src/",
143 | "phpcpd": "./vendor/bin/phpcpd src/",
144 | "phpcs": "./vendor/bin/phpcs --extensions=php --standard=PSR1,PSR2 --exclude=PSR2.Classes.ClassDeclaration,Squiz.Functions.MultiLineFunctionDeclaration bin/ src/",
145 | "phploc": "./vendor/bin/phploc --exclude=vendor --exclude=docs .",
146 | "phpmd": "./vendor/bin/phpmd src text cleancode,codesize,controversial,design,naming,unusedcode",
147 | "phpmnd": "./vendor/bin/phpmnd . --non-zero-exit-on-violation --exclude=vendor",
148 | "phpstan": "./vendor/bin/phpstan analyze -l max src",
149 | "phpunit": "./vendor/bin/phpunit --configuration ./phpunit.xml --testdox",
150 | "security-checker": "./vendor/bin/security-checker security:check ./composer.lock",
151 | "system-check": [
152 | "php --version",
153 | "php -m",
154 | "php --info | grep -E \"(max_execution_time|memory_limit)\"",
155 | "@composer --version",
156 | "@composer validate",
157 | "@composer diagnose || echo \"Ignore warnings\"",
158 | "@composer check-platform-reqs",
159 | "@composer config extra.version"
160 | ],
161 | "test": [
162 | "@composer is-built",
163 | "./`composer config extra.name` --help",
164 | "./`composer config extra.name` --version",
165 | "for command in $(./`composer config extra.name` list 2> /dev/null | awk '{print $1}'); do echo Run \"./`composer config extra.name` $command --help\" ; ./`composer config extra.name` \"$command\" --help > /dev/null || echo \"Command $command failed with non-zero exit code\"; done"
166 | ],
167 | "win-ci": [
168 | "@composer win-system-check",
169 | "@composer win-build"
170 | ],
171 | "win-build": [
172 | "@composer dump-autoload --classmap-authoritative",
173 | ".\\vendor\\bin\\phar-builder.bat package composer.json",
174 | "powershell mv \"$(composer config extra.name).phar\" \"$(composer config extra.name)\"",
175 | "@composer dump-autoload"
176 | ],
177 | "win-system-check": [
178 | "php --version",
179 | "php -m",
180 | "@composer --version",
181 | "@composer validate",
182 | "@composer diagnose & exit 0",
183 | "@composer check-platform-reqs",
184 | "@composer config extra.version"
185 | ]
186 | },
187 | "scripts-descriptions": {
188 | "ci": "Perform continuous integration tasks",
189 | "clean": "Cleanup project directory",
190 | "build": "Create a binary",
191 | "deb": "Create a Debian GNU/Linux package",
192 | "dist": "Create a distribution tarball",
193 | "find-forbidden": "Find forbidden words in source code",
194 | "gitstats": "Create Git statistics",
195 | "gource": "Visualize Git history",
196 | "is-built": "Test whether binary is already built",
197 | "lint": "Perform all lint checks",
198 | "lint-php": "Check syntax of PHP files",
199 | "lint-json": "Check syntax of JSON files",
200 | "lint-xml": "Check syntax of XML files",
201 | "lint-yaml": "Check syntax of YAML files",
202 | "phive": "Build PHIVE files",
203 | "phpcompatibility": "Run PHP compatibility checks",
204 | "phpcpd": "Detect copy/paste in source code",
205 | "phpcs": "Detect violations of defined coding standards",
206 | "phploc": "Print source code statistics",
207 | "phpmd": "Detect mess in source code",
208 | "phpmnd": "Detect magic numbers in source code",
209 | "phpstan": "Analyze source code",
210 | "phpunit": "Perform unit tests",
211 | "security-checker": "Look for dependencies with known security vulnerabilities",
212 | "system-check": "Run some system checks",
213 | "test": "Perform some tests with the built binary"
214 | },
215 | "extra": {
216 | "name": "idoitcli",
217 | "version": "0.9",
218 | "phar-builder": {
219 | "skip-shebang": false,
220 | "include-dev": false,
221 | "entry-point": "bin/idoitcli.php",
222 | "compression": "GZip",
223 | "name": "idoitcli.phar",
224 | "output-dir": "./",
225 | "include": ["config"]
226 | }
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/config/applications.json:
--------------------------------------------------------------------------------
1 | {
2 | "applications": {
3 | "amount": 1000,
4 | "objectType": "C__OBJTYPE__APPLICATION",
5 | "prefix": "app-"
6 | },
7 | "licenses": {
8 | "amount": 10,
9 | "objectType": "C__OBJTYPE__LICENCE",
10 | "prefix": "license-"
11 | },
12 | "laptops": {
13 | "amount": 700,
14 | "objectType": "C__OBJTYPE__CLIENT",
15 | "prefix": "laptop-"
16 | },
17 | "installApplications": {
18 | "min": 100,
19 | "max": 1000,
20 | "addLicense": true
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/config/dcim.json:
--------------------------------------------------------------------------------
1 | {
2 | "racks": {
3 | "amount": 15,
4 | "prefix": "rack-",
5 | "rackUnits": 47,
6 | "verticalSlots": 2
7 | },
8 | "servers": {
9 | "amount": 100,
10 | "prefix": "srv-",
11 | "rackUnits": {
12 | "min": 1,
13 | "max": 4
14 | },
15 | "model": [
16 | {
17 | "manufacturer": "Hell",
18 | "title": "PowerBadge R415"
19 | },
20 | {
21 | "manufacturer": "Venolo",
22 | "title": "y3650"
23 | },
24 | {
25 | "manufacturer": "Hans-Peter",
26 | "title": "ProLiar DL585"
27 | }
28 | ],
29 | "cpu": {
30 | "amount": 2,
31 | "manufacturer": "Amdtel",
32 | "type": "Neon",
33 | "cores": 12,
34 | "frequency": 2.4
35 | },
36 | "memory": {
37 | "amount": 16,
38 | "type": "DDR4 ECC",
39 | "capacity": "16 GB"
40 | },
41 | "hdd": {
42 | "amount": 4,
43 | "raid": 6,
44 | "capacity": "4 TB"
45 | },
46 | "power": {
47 | "amount": 2,
48 | "watt": 200
49 | }
50 | },
51 | "density": {
52 | "rackUnits": 0.6,
53 | "racksPerRoom": {
54 | "min": 1,
55 | "max": 4
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/config/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "api": {
3 | "url": "",
4 | "key": "",
5 | "language": "en"
6 | },
7 | "cacheLifetime": 15768000,
8 | "limitBatchRequests": 0,
9 | "types": {
10 | "countries": "C__OBJTYPE__COUNTRY",
11 | "cities": "C__OBJTYPE__CITY",
12 | "buildings": "C__OBJTYPE__BUILDING",
13 | "rooms": "C__OBJTYPE__ROOM",
14 | "racks": "C__OBJTYPE__ENCLOSURE",
15 | "subnets": "C__OBJTYPE__LAYER3_NET",
16 | "servers": "C__OBJTYPE__SERVER",
17 | "persons": "C__OBJTYPE__PERSON",
18 | "desks": "C__OBJTYPE__WORKSTATION"
19 | },
20 | "fixip": {
21 | "blacklistedObjectTypes": [
22 | "C__OBJTYPE__SERVICE",
23 | "C__OBJTYPE__APPLICATION",
24 | "C__OBJTYPE__LICENCE",
25 | "C__OBJTYPE__OPERATING_SYSTEM",
26 | "C__OBJTYPE__DBMS",
27 | "C__OBJTYPE__DATABASE_SCHEMA",
28 | "C__OBJTYPE__DATABASE_INSTANCE",
29 | "C__OBJTYPE__BUILDING",
30 | "C__OBJTYPE__ROOM",
31 | "C__OBJTYPE__CABLE",
32 | "C__OBJTYPE__WIRING_SYSTEM",
33 | "C__OBJTYPE__COUNTRY",
34 | "C__OBJECT_TYPE__FLOOR",
35 | "C__CMDB__OBJTYPE__CABLE_TRAY",
36 | "C__CMDB__OBJTYPE__CONDUIT",
37 | "C__OBJECT_TYPE__GROUP",
38 | "C__OBJTYPE__VEHICLE",
39 | "C__OBJTYPE__AIRCRAFT",
40 | "C__OBJTYPE__INFORMATION_DOMAIN",
41 | "C__OBJTYPE__ORGANIZATION",
42 | "C__OBJTYPE__PERSON",
43 | "C__OBJTYPE__PERSON_GROUP",
44 | "C__OBJTYPE__NAGIOS_SERVICE_TPL",
45 | "C__OBJTYPE__SIM_CARD",
46 | "C__OBJTYPE__EMERGENCY_PLAN",
47 | "C__OBJTYPE__MAINTENANCE",
48 | "C__OBJTYPE__FILE",
49 | "C__OBJTYPE__IT_SERVICE",
50 | "C__OBJTYPE__WAN",
51 | "C__OBJTYPE__LAYER3_NET",
52 | "C__OBJTYPE__LAYER2_NET",
53 | "C__OBJTYPE__SUPERNET"
54 | ],
55 | "unproperSubnets": [
56 | "Global v4",
57 | "Global v6"
58 | ]
59 | },
60 | "log": {
61 | "colorize": true,
62 | "verbosity": 31
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/config/networks.json:
--------------------------------------------------------------------------------
1 | {
2 | "subnets": {
3 | "10.0.1.0/24": {
4 | "type": "IPv4",
5 | "address": "10.0.1.0",
6 | "mask": "255.255.255.0"
7 | },
8 | "10.0.2.0/24": {
9 | "type": "IPv4",
10 | "address": "10.0.2.0",
11 | "mask": "255.255.255.0"
12 | },
13 | "10.0.3.0/24": {
14 | "type": "IPv4",
15 | "address": "10.0.3.0",
16 | "mask": "255.255.255.0"
17 | },
18 | "10.0.4.0/24": {
19 | "type": "IPv4",
20 | "address": "10.0.4.0",
21 | "mask": "255.255.255.0"
22 | },
23 | "10.0.5.0/24": {
24 | "type": "IPv4",
25 | "address": "10.0.5.0",
26 | "mask": "255.255.255.0"
27 | },
28 | "10.0.6.0/24": {
29 | "type": "IPv4",
30 | "address": "10.0.6.0",
31 | "mask": "255.255.255.0"
32 | },
33 | "10.0.7.0/24": {
34 | "type": "IPv4",
35 | "address": "10.0.7.0",
36 | "mask": "255.255.255.0"
37 | },
38 | "10.0.8.0/24": {
39 | "type": "IPv4",
40 | "address": "10.0.8.0",
41 | "mask": "255.255.255.0"
42 | },
43 | "10.0.9.0/24": {
44 | "type": "IPv4",
45 | "address": "10.0.9.0",
46 | "mask": "255.255.255.0"
47 | },
48 | "10.0.10.0/24": {
49 | "type": "IPv4",
50 | "address": "10.0.10.0",
51 | "mask": "255.255.255.0"
52 | },
53 | "10.0.11.0/24": {
54 | "type": "IPv4",
55 | "address": "10.0.11.0",
56 | "mask": "255.255.255.0"
57 | },
58 | "10.0.12.0/24": {
59 | "type": "IPv4",
60 | "address": "10.0.12.0",
61 | "mask": "255.255.255.0"
62 | },
63 | "10.0.13.0/24": {
64 | "type": "IPv4",
65 | "address": "10.0.13.0",
66 | "mask": "255.255.255.0"
67 | },
68 | "10.0.14.0/24": {
69 | "type": "IPv4",
70 | "address": "10.0.14.0",
71 | "mask": "255.255.255.0"
72 | },
73 | "10.0.15.0/24": {
74 | "type": "IPv4",
75 | "address": "10.0.15.0",
76 | "mask": "255.255.255.0"
77 | },
78 | "10.0.16.0/24": {
79 | "type": "IPv4",
80 | "address": "10.0.16.0",
81 | "mask": "255.255.255.0"
82 | },
83 | "10.0.17.0/24": {
84 | "type": "IPv4",
85 | "address": "10.0.17.0",
86 | "mask": "255.255.255.0"
87 | },
88 | "10.0.18.0/24": {
89 | "type": "IPv4",
90 | "address": "10.0.18.0",
91 | "mask": "255.255.255.0"
92 | },
93 | "10.0.19.0/24": {
94 | "type": "IPv4",
95 | "address": "10.0.19.0",
96 | "mask": "255.255.255.0"
97 | },
98 | "10.0.20.0/24": {
99 | "type": "IPv4",
100 | "address": "10.0.20.0",
101 | "mask": "255.255.255.0"
102 | },
103 | "10.1.0.0/16": {
104 | "type": "IPv4",
105 | "address": "10.1.0.0",
106 | "mask": "255.255.0.0"
107 | },
108 | "10.2.0.0/16": {
109 | "type": "IPv4",
110 | "address": "10.2.0.0",
111 | "mask": "255.255.0.0"
112 | },
113 | "10.3.0.0/16": {
114 | "type": "IPv4",
115 | "address": "10.3.0.0",
116 | "mask": "255.255.0.0"
117 | },
118 | "10.4.0.0/16": {
119 | "type": "IPv4",
120 | "address": "10.4.0.0",
121 | "mask": "255.255.0.0"
122 | },
123 | "10.5.0.0/16": {
124 | "type": "IPv4",
125 | "address": "10.5.0.0",
126 | "mask": "255.255.0.0"
127 | },
128 | "10.6.0.0/16": {
129 | "type": "IPv4",
130 | "address": "10.6.0.0",
131 | "mask": "255.255.0.0"
132 | },
133 | "10.7.0.0/16": {
134 | "type": "IPv4",
135 | "address": "10.7.0.0",
136 | "mask": "255.255.0.0"
137 | },
138 | "10.8.0.0/16": {
139 | "type": "IPv4",
140 | "address": "10.8.0.0",
141 | "mask": "255.255.0.0"
142 | },
143 | "10.9.0.0/16": {
144 | "type": "IPv4",
145 | "address": "10.9.0.0",
146 | "mask": "255.255.0.0"
147 | },
148 | "10.10.0.0/16": {
149 | "type": "IPv4",
150 | "address": "10.10.0.0",
151 | "mask": "255.255.0.0"
152 | }
153 | },
154 | "servers": {
155 | "amount": 30000,
156 | "prefix": "srv-",
157 | "ips": 4
158 | },
159 | "density": {
160 | "subnets": 0.8
161 | }
162 | }
--------------------------------------------------------------------------------
/config/schema.json:
--------------------------------------------------------------------------------
1 | [
2 | {"key": "api", "type": "object", "required": true, "nodes": [
3 | {"key": "url", "type": "string", "required": true},
4 | {"key": "key", "type": "string", "required": true},
5 | {"key": "username", "type": "string", "required": false, "minLength": 1},
6 | {"key": "password", "type": "string", "required": false, "minLength": 1},
7 | {"key": "language", "type": "string", "required": false, "values": ["en", "de"]},
8 | {"key": "proxy", "type": "object", "required": false, "nodes": [
9 | {"key": "type", "type": "string", "required": true, "values": ["HTTP", "SOCKS5"]},
10 | {"key": "host", "type": "string", "required": false, "minLength": 1},
11 | {"key": "port", "type": "integer", "required": false, "ge": 1, "le": 65536},
12 | {"key": "username", "type": "string", "required": false, "minLength": 1},
13 | {"key": "password", "type": "string", "required": false, "minLength": 1},
14 | {"key": "active", "type": "boolean", "required": true}
15 | ]}
16 | ]},
17 | {"key": "cacheLifetime", "type": "integer", "required": true, "ge": 0},
18 | {"key": "limitBatchRequests", "type": "integer", "required": true, "ge": 0},
19 | {"key": "types", "type": "object", "required": false, "nodes": [
20 | {"key": "countries", "type": "string", "required": true, "minLength": 1},
21 | {"key": "cities", "type": "string", "required": true, "minLength": 1},
22 | {"key": "buildings", "type": "string", "required": true, "minLength": 1},
23 | {"key": "rooms", "type": "string", "required": true, "minLength": 1},
24 | {"key": "racks", "type": "string", "required": true, "minLength": 1},
25 | {"key": "subnets", "type": "string", "required": true, "minLength": 1},
26 | {"key": "servers", "type": "string", "required": true, "minLength": 1},
27 | {"key": "persons", "type": "string", "required": true, "minLength": 1},
28 | {"key": "desks", "type": "string", "required": true, "minLength": 1}
29 | ]},
30 | {"key": "fixip", "type": "object", "required": false, "nodes": [
31 | {"key": "blacklistedObjectTypes", "type": "array", "required": true, "items": "string", "minCount": 0, "minLength": 1},
32 | {"key": "unproperSubnets", "type": "array", "required": true, "items": "string", "minCount": 0, "minLength": 1}
33 |
34 | ]},
35 | {"key": "log", "type": "object", "required": false, "nodes": [
36 | {"key": "colorize", "type": "boolean", "required": true},
37 | {"key": "verbosity", "type": "integer", "required": true, "ge": 1, "le": 63}
38 | ]}
39 | ]
40 |
--------------------------------------------------------------------------------
/config/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "templates": {
3 | "server": [
4 | {
5 | "category": "model",
6 | "attribute": "manufacturer"
7 | },
8 | {
9 | "category": "model",
10 | "attribute": "model"
11 | },
12 | {
13 | "category": "host address",
14 | "attribute": "ipv4_address"
15 | },
16 | {
17 | "category": "host address",
18 | "attribute": "hostname"
19 | },
20 | {
21 | "category": "host address",
22 | "attribute": "domain"
23 | },
24 | {
25 | "category": "location",
26 | "attribute": "location",
27 | "default": "Root location"
28 | }
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/idoit.bash-completion:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Copyright (C) 2016-18 Benjamin Heisig
4 | ##
5 | ## This program is free software: you can redistribute it and/or modify
6 | ## it under the terms of the GNU Affero General Public License as published by
7 | ## the Free Software Foundation, either version 3 of the License, or
8 | ## (at your option) any later version.
9 | ##
10 | ## This program is distributed in the hope that it will be useful,
11 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | ## GNU Affero General Public License for more details.
14 | ##
15 | ## You should have received a copy of the GNU Affero General Public License
16 | ## along with this program. If not, see .
17 |
18 | _idoit_command() {
19 | local cur options commands
20 | COMPREPLY=()
21 | cur="${COMP_WORDS[COMP_CWORD]}"
22 | prev="${COMP_WORDS[COMP_CWORD-1]}"
23 | options="-c -h --config --help --version"
24 | commands="init status read show search random nextip fixip help categories types"
25 |
26 | case "$prev" in
27 | read)
28 | local path=`idoit read $2 2> /dev/null | sed ':a;{N;s/\n/ /};ba'`
29 | COMPREPLY=( $( compgen -W "$path" -- $cur ) )
30 | #local files=( $(idoit read $2 2> /dev/null) )
31 | #local files=("/tmp/$2"*)
32 | #[[ -e ${files[0]} ]] && COMPREPLY=( "${files[@]##*/}" )
33 | return 0
34 | ;;
35 | esac
36 |
37 | case "$cur" in
38 | -*) COMPREPLY=( $( compgen -W "${options}" -- $cur ) );;
39 | *) COMPREPLY=( $( compgen -W "${commands}" -- $cur ) );;
40 | esac
41 |
42 | return 0
43 | }
44 |
45 | complete -F _idoit_command idoit
46 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | ../tests/
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ../src/
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/Command/Cache.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \DirectoryIterator;
30 | use \Exception;
31 |
32 | /**
33 | * Command "cache"
34 | */
35 | class Cache extends Command {
36 |
37 | /**
38 | * Execute command
39 | *
40 | * @return self Returns itself
41 | *
42 | * @throws Exception on error
43 | */
44 | public function execute(): self {
45 | $this->log->info($this->getDescription());
46 |
47 | $this
48 | ->clearCache()
49 | ->createCache();
50 |
51 | return $this;
52 | }
53 |
54 | /**
55 | * @param string $file
56 | * @param mixed $value
57 | *
58 | * @return self Returns itself
59 | *
60 | * @throws Exception on error
61 | */
62 | protected function serialize(string $file, $value): self {
63 | $filePath = $this->useCache()->getHostDir() . '/' . $file;
64 |
65 | $status = file_put_contents($filePath, serialize($value));
66 |
67 | if ($status === false) {
68 | throw new Exception(sprintf(
69 | 'Unable to write to cache file "%s"',
70 | $filePath
71 | ));
72 | }
73 |
74 | return $this;
75 | }
76 |
77 | /**
78 | * @return self Returns itself
79 | *
80 | * @throws Exception on error
81 | */
82 | protected function clearCache(): self {
83 | $hostDir = $this->useCache()->getHostDir();
84 |
85 | if (!is_dir($hostDir)) {
86 | $this->log->info(
87 | 'Create cache directory for i-doit instance "%s"',
88 | $this->config['api']['url']
89 | );
90 |
91 | $status = mkdir($hostDir, 0775, true);
92 |
93 | if ($status === false) {
94 | throw new Exception(sprintf(
95 | 'Unable to create data directory "%s"',
96 | $hostDir
97 | ));
98 | }
99 | } else {
100 | $this->log->info('Clear cache files');
101 |
102 | $files = new DirectoryIterator($hostDir);
103 |
104 | foreach ($files as $file) {
105 | if ($file->isFile()) {
106 | $status = @unlink($file->getPathname());
107 |
108 | if ($status === false) {
109 | throw new Exception(sprintf(
110 | 'Unable to clear data in "%s". Unable to delete file "%s"',
111 | $hostDir,
112 | $file->getPathname()
113 | ));
114 | }
115 | }
116 | }
117 | }
118 |
119 | $this->log->debug(
120 | ' Stored cache files in directory %s',
121 | $hostDir
122 | );
123 |
124 | return $this;
125 | }
126 |
127 | /**
128 | * @return self Returns itself
129 | *
130 | * @throws Exception on error
131 | */
132 | protected function createCache(): self {
133 | $this->log->info('Fetch list of object types');
134 |
135 | $objectTypes = $this->useIdoitAPIFactory()->getCMDBObjectTypes()->read();
136 |
137 | switch (count($objectTypes)) {
138 | case 0:
139 | $this->log->notice(
140 | ' Found no object types'
141 | );
142 | break;
143 | case 1:
144 | $this->log->debug(
145 | ' Found 1 object type'
146 | );
147 | break;
148 | default:
149 | $this->log->debug(
150 | ' Found %s object types',
151 | count($objectTypes)
152 | );
153 | break;
154 | }
155 |
156 | $this->serialize(
157 | 'object_types',
158 | $objectTypes
159 | );
160 |
161 | $this->log->info('Fetch list of assigned categories');
162 |
163 | $objectTypeIDs = [];
164 |
165 | foreach ($objectTypes as $objectType) {
166 | $objectTypeIDs[] = (int) $objectType['id'];
167 | }
168 |
169 | $batchedAssignedCategories = $this
170 | ->useIdoitAPIFactory()
171 | ->getCMDBObjectTypeCategories()
172 | ->batchReadByID($objectTypeIDs);
173 |
174 | for ($i = 0; $i < count($objectTypes); $i++) {
175 | switch (count($batchedAssignedCategories[$i])) {
176 | case 0:
177 | $this->log->notice(
178 | ' Object type "%s" [%s] has no categories assigned',
179 | $objectTypes[$i]['title'],
180 | $objectTypes[$i]['const']
181 | );
182 | break;
183 | case 1:
184 | $this->log->debug(
185 | ' Object type "%s" [%s] has 1 category assigned',
186 | $objectTypes[$i]['title'],
187 | $objectTypes[$i]['const']
188 | );
189 | break;
190 | default:
191 | $this->log->debug(
192 | ' Object type "%s" [%s] has %s categories assigned',
193 | $objectTypes[$i]['title'],
194 | $objectTypes[$i]['const'],
195 | count($batchedAssignedCategories[$i])
196 | );
197 | break;
198 | }
199 |
200 | $this->serialize(
201 | sprintf(
202 | 'object_type__%s',
203 | $objectTypes[$i]['const']
204 | ),
205 | $batchedAssignedCategories[$i]
206 | );
207 | }
208 |
209 | $this->log->info('Fetch information about categories');
210 |
211 | $categoryConsts = [];
212 |
213 | $categories = [];
214 |
215 | $blacklistedCategories = $this
216 | ->useIdoitAPIFactory()
217 | ->getCMDBCategoryInfo()
218 | ->getVirtualCategoryConstants();
219 |
220 | foreach ($batchedAssignedCategories as $assignedCategories) {
221 | foreach ($assignedCategories as $type => $categoryList) {
222 | foreach ($categoryList as $category) {
223 | if (in_array($category['const'], $blacklistedCategories)) {
224 | continue;
225 | }
226 |
227 | if (array_key_exists($category['const'], $categories)) {
228 | continue;
229 | }
230 |
231 | $categoryConsts[] = $category['const'];
232 | $categories[$category['const']] = $category;
233 | }
234 | }
235 | }
236 |
237 | switch (count($categoryConsts)) {
238 | case 0:
239 | $this->log->notice(
240 | ' Found no information about assigned categories'
241 | );
242 | break;
243 | case 1:
244 | $this->log->debug(
245 | ' Found information about 1 category'
246 | );
247 | break;
248 | default:
249 | $this->log->debug(
250 | ' Found information about %s categories',
251 | count($categoryConsts)
252 | );
253 | break;
254 | }
255 |
256 | $propertyCounter = 0;
257 |
258 | if (count($categoryConsts) > 0) {
259 | $batchCategoryInfo = $this
260 | ->useIdoitAPIFactory()
261 | ->getCMDBCategoryInfo()
262 | ->batchRead($categoryConsts);
263 |
264 | $counter = 0;
265 |
266 | foreach ($categoryConsts as $categoryConst) {
267 | $categories[$categoryConst]['properties'] = $batchCategoryInfo[$counter];
268 |
269 | $propertyCounter += count($batchCategoryInfo[$counter]);
270 |
271 | switch (count($batchCategoryInfo[$counter])) {
272 | case 0:
273 | $this->log->debug(
274 | ' Category "%s" [%s] has no properties',
275 | $categories[$categoryConst]['title'],
276 | $categoryConst
277 | );
278 | break;
279 | case 1:
280 | $this->log->debug(
281 | ' Category "%s" [%s] has 1 property',
282 | $categories[$categoryConst]['title'],
283 | $categoryConst
284 | );
285 | break;
286 | default:
287 | $this->log->debug(
288 | ' Category "%s" [%s] has %s properties',
289 | $categories[$categoryConst]['title'],
290 | $categoryConst,
291 | count($batchCategoryInfo[$counter])
292 | );
293 | break;
294 | }
295 |
296 | if (count($batchCategoryInfo[$counter]) > 0) {
297 | $this->serialize(
298 | sprintf(
299 | 'category__%s',
300 | $categoryConst
301 | ),
302 | $categories[$categoryConst]
303 | );
304 | }
305 |
306 | $counter++;
307 | }
308 | }
309 |
310 | switch ($propertyCounter) {
311 | case 0:
312 | $this->log->notice(
313 | 'Found no properties over all categories'
314 | );
315 | break;
316 | case 1:
317 | $this->log->debug(
318 | 'Found 1 property over all categories'
319 | );
320 | break;
321 | default:
322 | $this->log->debug(
323 | 'Found %s properties over all categories',
324 | $propertyCounter
325 | );
326 | break;
327 | }
328 |
329 | return $this;
330 | }
331 |
332 | }
333 |
--------------------------------------------------------------------------------
/src/Command/Call.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 | use \bheisig\cli\IO;
31 |
32 | /**
33 | * Command "call"
34 | */
35 | class Call extends Command {
36 |
37 | /**
38 | * Execute command
39 | *
40 | * @return self Returns itself
41 | *
42 | * @throws Exception on error
43 | */
44 | public function execute(): self {
45 | $query = $this->getQuery();
46 |
47 | if ($query === '') {
48 | while (true) {
49 | $line = IO::in('');
50 |
51 | if ($line === '') {
52 | break;
53 | }
54 |
55 | $query .= $line;
56 | }
57 | }
58 |
59 | $request = json_decode(trim($query), true);
60 |
61 | if (!is_array($request)) {
62 | throw new Exception('No valid JSON input');
63 | }
64 |
65 | if (!array_key_exists('method', $request)) {
66 | throw new Exception('No RPC method specified');
67 | }
68 |
69 | $params = null;
70 |
71 | if (array_key_exists('params', $request)) {
72 | $params = $request['params'];
73 | }
74 |
75 | $this->useIdoitAPIFactory()->getAPI()->request($request['method'], $params);
76 |
77 | $requestContent = json_encode(
78 | $this->useIdoitAPIFactory()->getAPI()->getLastRequestContent(),
79 | JSON_PRETTY_PRINT
80 | );
81 |
82 | if (!is_string($requestContent)) {
83 | $requestContent = '–';
84 | }
85 |
86 | $responseContent = json_encode(
87 | $this->useIdoitAPIFactory()->getAPI()->getLastResponse(),
88 | JSON_PRETTY_PRINT
89 | );
90 |
91 | if (!is_string($responseContent)) {
92 | $responseContent = '–';
93 | }
94 |
95 | IO::out('');
96 | IO::out('Request:');
97 | IO::out('========');
98 | IO::out($this->useIdoitAPIFactory()->getAPI()->getLastRequestHeaders());
99 | IO::out($requestContent);
100 | IO::out('');
101 | IO::out('Response:');
102 | IO::out('=========');
103 | IO::out($this->useIdoitAPIFactory()->getAPI()->getLastResponseHeaders());
104 | IO::out($responseContent);
105 |
106 | return $this;
107 | }
108 |
109 | /**
110 | * Print usage of command
111 | *
112 | * @return self Returns itself
113 | */
114 | public function printUsage(): self {
115 | $this->log->info(
116 | 'Usage: %1$s %2$s [OPTIONS] [REQUEST]
117 |
118 | %3$s
119 |
120 | REQUEST is a JSON-formatted string. Leave empty to read from standard input.
121 |
122 | Examples:
123 |
124 | 1) %1$s %2$s \\
125 | \'{"version": "2.0","method": "idoit.version","params": {"apikey": "c1ia5q","language": "en"},"id": 1}\'
126 | 2) echo \\
127 | \'{"version": "2.0","method": "idoit.version","params": {"apikey": "c1ia5q","language": "en"},"id": 1}\' \\
128 | | %1$s %2$s
129 | 3) cat request.txt | %1$s %2$s',
130 | $this->config['composer']['extra']['name'],
131 | $this->getName(),
132 | $this->getDescription()
133 | );
134 |
135 | return $this;
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/Command/Categories.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 |
31 | /**
32 | * Command "categories"
33 | */
34 | class Categories extends Command {
35 |
36 | /**
37 | * Execute command
38 | *
39 | * @return self Returns itself
40 | *
41 | * @throws Exception on error
42 | */
43 | public function execute(): self {
44 | $this->log
45 | ->printAsMessage()
46 | ->info($this->getDescription())
47 | ->printEmptyLine();
48 |
49 | $categories = $this->useCache()->getCategories();
50 |
51 | $categories = $this->filterCategories($categories);
52 |
53 | $types = [
54 | [
55 | 'type' => 'CATG',
56 | 'title' => 'Global categories',
57 | 'arg' => 'global',
58 | 'active' => true
59 | ],
60 | [
61 | 'type' => 'CATS',
62 | 'title' => 'Specific categories',
63 | 'arg' => 'specific',
64 | 'active' => true
65 | ]
66 | ];
67 |
68 | $keepActivated = [];
69 |
70 | foreach ($types as $type) {
71 | if (in_array('--' . $type['arg'], $this->config['args'])) {
72 | $keepActivated[] = $type['type'];
73 | }
74 | }
75 |
76 | if (count($keepActivated) > 0) {
77 | for ($i = 0; $i < count($types); $i++) {
78 | if (!in_array($types[$i]['type'], $keepActivated)) {
79 | $types[$i]['active'] = false;
80 | }
81 | }
82 | }
83 |
84 | foreach ($types as $typeDetails) {
85 | if ($typeDetails['active'] === true) {
86 | // Type casting is necessary because phpstan unfortunately throws an error:
87 | $this->formatList(
88 | (string) $typeDetails['title'],
89 | (string) $typeDetails['type'],
90 | $categories
91 | );
92 |
93 | $this->log
94 | ->printAsMessage()
95 | ->printEmptyLine();
96 | }
97 | }
98 |
99 | return $this;
100 | }
101 |
102 | /**
103 | *
104 | * @param array $categories
105 | *
106 | * @return array
107 | *
108 | * @throws Exception on error
109 | */
110 | protected function filterCategories(array $categories): array {
111 | $result = [];
112 |
113 | if (in_array('--enabled', $this->config['args'])) {
114 | $enabledCategories = $this->getEnabledCategories();
115 |
116 | foreach ($categories as $category) {
117 | if (in_array($category['const'], $enabledCategories)) {
118 | $result[] = $category;
119 | }
120 | }
121 | } elseif (in_array('--disabled', $this->config['args'])) {
122 | $enabledCategories = $this->getEnabledCategories();
123 |
124 | foreach ($categories as $category) {
125 | if (!in_array($category['const'], $enabledCategories)) {
126 | $result[] = $category;
127 | }
128 | }
129 | } else {
130 | $result = $categories;
131 | }
132 |
133 | return $result;
134 | }
135 |
136 | /**
137 | * Get list of categories which are assigned to one or more object types
138 | *
139 | * @return array
140 | *
141 | * @throws Exception on error
142 | */
143 | protected function getEnabledCategories(): array {
144 | $result = [];
145 |
146 | $objectTypes = $this->useCache()->getObjectTypes();
147 |
148 | foreach ($objectTypes as $objectType) {
149 | $assignedCategories = $this->useCache()->getAssignedCategories($objectType['const']);
150 |
151 | foreach ($assignedCategories as $type => $categories) {
152 | foreach ($categories as $category) {
153 | $result[] = $category['const'];
154 | }
155 | }
156 | }
157 |
158 | return array_unique($result);
159 | }
160 |
161 | /**
162 | *
163 | *
164 | * @param string $title
165 | * @param string $type
166 | * @param array $categories
167 | */
168 | protected function formatList(string $title, string $type, array $categories) {
169 | $this->log
170 | ->printAsMessage()
171 | ->info('%s [%s]:', $title, $type)
172 | ->printEmptyLine();
173 |
174 | $categories = $this->filterByType($categories, $type);
175 |
176 | switch (count($categories)) {
177 | case 0:
178 | $this->log
179 | ->printAsMessage()
180 | ->info('No categories found');
181 | break;
182 | case 1:
183 | $this->log
184 | ->printAsMessage()
185 | ->info('1 category found');
186 | break;
187 | default:
188 | $this->log
189 | ->printAsMessage()
190 | ->info('%s categories found', count($categories));
191 | break;
192 | }
193 |
194 | usort($categories, [$this, 'sortCategories']);
195 |
196 | foreach ($categories as $category) {
197 | $this->log
198 | ->printAsOutput()
199 | ->info($this->formatCategory($category));
200 | }
201 | }
202 |
203 | /**
204 | *
205 | *
206 | * @param array $categories
207 | * @param string $type
208 | *
209 | * @return array
210 | */
211 | protected function filterByType(array $categories, string $type): array {
212 | $result = [];
213 |
214 | foreach ($categories as $category) {
215 | $constPrefix = 'C__' . $type . '__';
216 | if (strpos($category['const'], $constPrefix) === 0) {
217 | $result[] = $category;
218 | }
219 | }
220 |
221 | return $result;
222 | }
223 |
224 | /**
225 | *
226 | *
227 | * @param array $category
228 | *
229 | * @return string
230 | */
231 | protected function formatCategory(array $category): string {
232 | return sprintf(
233 | '%s [%s]',
234 | $category['title'],
235 | $category['const']
236 | );
237 | }
238 |
239 | /**
240 | *
241 | *
242 | * @param array $a
243 | * @param array $b
244 | *
245 | * @return int
246 | */
247 | protected function sortCategories(array $a, array $b): int {
248 | return strcmp($a['title'], $b['title']);
249 | }
250 |
251 | /**
252 | * Print usage of command
253 | *
254 | * @return self Returns itself
255 | */
256 | public function printUsage(): self {
257 | $this->log->info(
258 | <<< EOF
259 | %3\$s
260 |
261 | USAGE
262 | \$ %1\$s %2\$s [OPTIONS]
263 |
264 | COMMAND OPTIONS
265 | --enabled Only list categories which are assigned to object
266 | types
267 | --disabled Only list categories which are not assigned to any
268 | object type
269 |
270 | --global Only list global categories
271 | --specific Only list specific categories
272 |
273 | COMMON OPTIONS
274 | -c FILE, Include settings stored in a JSON-formatted
275 | --config=FILE configuration file FILE; repeat option for more
276 | than one FILE
277 | -s KEY=VALUE, Add runtime setting KEY with its VALUE; separate
278 | --setting=KEY=VALUE nested keys with ".", for example "key1.key2=123";
279 | repeat option for more than one KEY
280 |
281 | --no-colors Do not print colored messages
282 | -q, --quiet Do not output messages, only errors
283 | -v, --verbose Be more verbose
284 |
285 | -h, --help Print this help or information about a
286 | specific command
287 | --version Print version information
288 |
289 | -y, --yes No user interaction required; answer questions
290 | automatically with default values
291 |
292 | EXAMPLES
293 | # List all available categories:
294 | \$ %1\$s %2\$s
295 |
296 | # List global categories which are assigned to one or more object types:
297 | \$ %1\$s %2\$s --enabled --global
298 | EOF
299 | ,
300 | $this->config['composer']['extra']['name'],
301 | $this->getName(),
302 | $this->getDescription()
303 | );
304 |
305 | return $this;
306 | }
307 |
308 | }
309 |
--------------------------------------------------------------------------------
/src/Command/Command.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 | use \BadMethodCallException;
31 | use \RuntimeException;
32 | use bheisig\cli\Command\Command as BaseCommand;
33 | use bheisig\cli\Config;
34 | use bheisig\cli\JSONFile;
35 | use bheisig\idoitcli\Service\Cache;
36 | use bheisig\idoitcli\Service\HandleAttribute;
37 | use bheisig\idoitcli\Service\IdoitAPI;
38 | use bheisig\idoitcli\Service\IdoitAPIFactory;
39 | use bheisig\idoitcli\Service\PrintData;
40 | use bheisig\idoitcli\Service\Validate;
41 |
42 | /**
43 | * Base class for commands
44 | */
45 | abstract class Command extends BaseCommand {
46 |
47 | /**
48 | * @var HandleAttribute
49 | */
50 | protected $handleAttribute;
51 |
52 | /**
53 | * i-doit API
54 | *
55 | * @var IdoitAPI
56 | */
57 | protected $idoitAPI;
58 |
59 | /**
60 | * i-doit API factory
61 | *
62 | * @var IdoitAPIFactory
63 | */
64 | protected $idoitAPIFactory;
65 |
66 | /**
67 | * @var Cache
68 | */
69 | protected $cache;
70 |
71 | /**
72 | * @var PrintData
73 | */
74 | protected $printData;
75 |
76 | /**
77 | * @var Validate
78 | */
79 | protected $validate;
80 |
81 | /**
82 | * Process some routines before executing command
83 | *
84 | * @return self Returns itself
85 | *
86 | * @throws Exception on error
87 | */
88 | public function setup(): self {
89 | parent::setup();
90 |
91 | $this->validateConfig();
92 |
93 | return $this;
94 | }
95 |
96 | /**
97 | * Load service for user interaction
98 | *
99 | * @return Cache
100 | *
101 | * @throws Exception when cache is out-dated
102 | */
103 | protected function useCache(): Cache {
104 | if (!isset($this->cache)) {
105 | $this->cache = new Cache($this->config, $this->log);
106 | }
107 |
108 | if ($this->cache->isCached() === false &&
109 | $this->config['command'] !== 'cache') {
110 | throw new RuntimeException(sprintf(
111 | 'Unsufficient data. Please run "%s cache" first.',
112 | $this->config['composer']['extra']['name']
113 | ), 500);
114 | }
115 |
116 | return $this->cache;
117 | }
118 |
119 | /**
120 | * Load service for attribute handling
121 | *
122 | * @return HandleAttribute
123 | *
124 | * @throws Exception on error
125 | */
126 | protected function handleAttribute(): HandleAttribute {
127 | if (!isset($this->handleAttribute)) {
128 | $this->handleAttribute = (new HandleAttribute($this->config, $this->log))
129 | ->setUp($this->useIdoitAPI(), $this->useIdoitAPIFactory());
130 | }
131 |
132 | return $this->handleAttribute;
133 | }
134 |
135 | /**
136 | * Load service for i-doit API
137 | *
138 | * @return IdoitAPI
139 | *
140 | * @throws Exception on error
141 | */
142 | protected function useIdoitAPI(): IdoitAPI {
143 | if (!isset($this->idoitAPI)) {
144 | $this->idoitAPI = new IdoitAPI($this->config, $this->log);
145 | $this->idoitAPI->setUp($this->useIdoitAPIFactory());
146 | }
147 |
148 | return $this->idoitAPI;
149 | }
150 |
151 | /**
152 | * Load service for i-doit API factory
153 | *
154 | * @return IdoitAPIFactory
155 | *
156 | * @throws Exception on error
157 | */
158 | protected function useIdoitAPIFactory(): IdoitAPIFactory {
159 | if (!isset($this->idoitAPIFactory)) {
160 | $this->idoitAPIFactory = new IdoitAPIFactory($this->config, $this->log);
161 | }
162 |
163 | return $this->idoitAPIFactory;
164 | }
165 |
166 | /**
167 | * Load service for attribute handling
168 | *
169 | * @return PrintData
170 | *
171 | * @throws Exception on error
172 | */
173 | protected function printData(): PrintData {
174 | if (!isset($this->printData)) {
175 | $this->printData = new PrintData($this->config, $this->log);
176 | }
177 |
178 | return $this->printData;
179 | }
180 |
181 | /**
182 | * Load service for validate data
183 | *
184 | * @return Validate
185 | */
186 | protected function useValidate(): Validate {
187 | if (!isset($this->validate)) {
188 | $this->validate = new Validate($this->config, $this->log);
189 | }
190 |
191 | return $this->validate;
192 | }
193 |
194 | /**
195 | * Print debug information
196 | *
197 | * @param array $arr
198 | *
199 | * @param int $nested
200 | */
201 | protected function printDebug(array $arr, int $nested = 0) {
202 | if (count($arr) === 0) {
203 | $this->log->debug(
204 | '%s–',
205 | str_repeat(' ', $nested * 4)
206 | );
207 |
208 | return;
209 | }
210 |
211 | foreach ($arr as $key => $value) {
212 | switch (gettype($value)) {
213 | case 'array':
214 | $this->log->debug(
215 | '%s%s:',
216 | str_repeat(' ', $nested * 4),
217 | $key
218 | );
219 |
220 | $this->printDebug($value, ($nested + 1));
221 | break;
222 | default:
223 | $this->log->debug(
224 | '%s%s: %s',
225 | str_repeat(' ', $nested * 4),
226 | $key,
227 | $value
228 | );
229 | break;
230 | }
231 | }
232 | }
233 |
234 | /**
235 | * @return array Object
236 | * @throws Exception on error
237 | */
238 | protected function identifyObjectByArgument(): array {
239 | switch (count($this->config['arguments'])) {
240 | case 0:
241 | if ($this->useUserInteraction()->isInteractive() === false) {
242 | throw new BadMethodCallException(
243 | 'No object, no visuals'
244 | );
245 | }
246 |
247 | return $this->askForObject();
248 | case 1:
249 | return $this->useIdoitAPI()->identifyObject(
250 | $this->config['arguments'][0]
251 | );
252 | default:
253 | throw new BadMethodCallException(
254 | 'Too many arguments; please provide only one object title or numeric identifier'
255 | );
256 | }
257 | }
258 |
259 | /**
260 | * Ask user for object title or numeric identifier
261 | *
262 | * …until object has been identified
263 | *
264 | * @return array Object information, otherwise \Exception is thrown
265 | *
266 | * @throws Exception on error
267 | */
268 | protected function askForObject(): array {
269 | $answer = $this->useUserInteraction()->askQuestion('Object?');
270 |
271 | if (strlen($answer) === 0) {
272 | $this->log->warning('Please re-try');
273 | return $this->askForObject();
274 | }
275 |
276 | try {
277 | return $this->useIdoitAPI()->identifyObject($answer);
278 | } catch (BadMethodCallException $e) {
279 | $this->log->warning($e->getMessage());
280 | $this->log->warning('Please re-try');
281 | return $this->askForObject();
282 | }
283 | }
284 |
285 | /**
286 | * Validate configuration settings
287 | *
288 | * @return self Returns itself
289 | *
290 | * @throws Exception on error
291 | */
292 | protected function validateConfig(): self {
293 | $file = $this->config['appDir'] . '/config/schema.json';
294 | $rules = JSONFile::read($file);
295 | $config = new Config();
296 | $errors = $config->validate($this->config, $rules);
297 |
298 | if (count($errors) > 0) {
299 | $this->log->warning('One or more errors found in configuration settings:');
300 |
301 | foreach ($errors as $error) {
302 | $this->log->warning($error);
303 | }
304 |
305 | throw new Exception('Cannot proceed unless you fix your configuration');
306 | }
307 |
308 | return $this;
309 | }
310 |
311 | }
312 |
--------------------------------------------------------------------------------
/src/Command/Create.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 |
31 | /**
32 | * Command "create"
33 | */
34 | class Create extends Command {
35 |
36 | /**
37 | * Path
38 | *
39 | * @var array Indexed array
40 | */
41 | protected $path = [];
42 |
43 | /**
44 | * Object type constant in path
45 | *
46 | * @var string
47 | */
48 | protected $objectTypeConst;
49 |
50 | /**
51 | * Processes some routines before the execution
52 | *
53 | * @return self Returns itself
54 | *
55 | * @throws Exception on error
56 | */
57 | public function setup(): Command {
58 | parent::setup();
59 |
60 | $this->path = explode('/', $this->getQuery());
61 |
62 | $objectTypes = $this->useCache()->getObjectTypes();
63 |
64 | foreach ($objectTypes as $objectType) {
65 | if (strtolower($objectType['title']) === strtolower($this->path[0])) {
66 | $this->objectTypeConst = $objectType['const'];
67 | break;
68 | }
69 | }
70 |
71 | $this->log->warning('This command is deprecated. Please use command "save" instead.');
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * Execute command
78 | *
79 | * @return self Returns itself
80 | *
81 | * @throws Exception on error
82 | */
83 | public function execute(): self {
84 | switch (count($this->path)) {
85 | case 1:
86 | throw new Exception('Path has only 1 part');
87 | case 2:
88 | if (isset($this->objectTypeConst)) {
89 | $objectID = $this->useIdoitAPIFactory()->getCMDBObject()->create(
90 | $this->objectTypeConst,
91 | $this->path[1]
92 | );
93 |
94 | $this->log->info('Object with ID %s successfully created', $objectID);
95 | } else {
96 | $objects = $this->useIdoitAPI()->fetchObjects(['title' => $this->path[0]]);
97 |
98 | $this->createCategoryEntries($objects, $this->getCategoryConst($this->path[1]));
99 | }
100 | break;
101 | case 3:
102 | $objects = $this->useIdoitAPI()->fetchObjects([
103 | 'type' => $this->objectTypeConst,
104 | 'title' => $this->path[1]
105 | ]);
106 |
107 | $this->createCategoryEntries($objects, $this->getCategoryConst($this->path[2]));
108 | break;
109 | default:
110 | throw new Exception('Path has too many parts');
111 | }
112 |
113 | return $this;
114 | }
115 |
116 | /**
117 | *
118 | *
119 | * @param array $objects
120 | * @param string $category
121 | *
122 | * @throws Exception on error
123 | */
124 | protected function createCategoryEntries(array $objects, string $category) {
125 | switch (count($objects)) {
126 | case 0:
127 | throw new Exception('No object found');
128 | }
129 |
130 | $attributes = $this->getAttributes();
131 |
132 | $objectIDs = array_map(function ($object) {
133 | return (int) $object['id'];
134 | }, $objects);
135 |
136 | $this->useIdoitAPIFactory()->getCMDBCategory()->batchCreate(
137 | $objectIDs,
138 | $category,
139 | [$attributes]
140 | );
141 |
142 | switch (count($objects)) {
143 | case 1:
144 | $this->log->info('Created 1 entry');
145 | break;
146 | default:
147 | $this->log->info('Created %s entries', count($objects));
148 | break;
149 | }
150 | }
151 |
152 | /**
153 | *
154 | *
155 | * @param string $category
156 | *
157 | * @return string
158 | *
159 | * @throws Exception on error
160 | */
161 | protected function getCategoryConst(string $category): string {
162 | $categories = $this->useCache()->getCategories();
163 |
164 | $candidates = [];
165 |
166 | foreach ($categories as $categoryInfo) {
167 | if (strtolower($categoryInfo['title']) === strtolower($category)) {
168 | $candidates[] = $categoryInfo;
169 | }
170 |
171 | if (strtolower($categoryInfo['const']) === strtolower($category)) {
172 | return $category;
173 | }
174 | }
175 |
176 | switch (count($candidates)) {
177 | case 0:
178 | throw new Exception(sprintf(
179 | 'Unknown category "%s"',
180 | $category
181 | ));
182 | case 1:
183 | return $candidates[0]['const'];
184 | default:
185 | $this->log->warning('Unambigious category title:');
186 |
187 | foreach ($candidates as $candidate) {
188 | $this->log->warning(
189 | ' "%s" [%s]',
190 | $candidate['title'],
191 | $candidate['const']
192 | );
193 | }
194 |
195 | throw new Exception('Unable to create one or more category entries');
196 | }
197 | }
198 |
199 | /**
200 | * @todo This argument parsing is pretty ugly.
201 | *
202 | * @return array
203 | */
204 | protected function getAttributes(): array {
205 | $attributes = [];
206 |
207 | $pathKey = array_search($this->getQuery(), $this->config['args']);
208 | $options = array_slice(
209 | $this->config['args'],
210 | ++$pathKey
211 | );
212 |
213 | for ($i = 0; $i < count($options); $i+=2) {
214 | $attribute = substr($options[$i], 2);
215 |
216 | if (array_key_exists(($i+1), $options)) {
217 | $value = $options[($i + 1)];
218 | $attributes[$attribute] = $value;
219 | }
220 | }
221 |
222 | return $attributes;
223 | }
224 |
225 | /**
226 | * Print usage of command
227 | *
228 | * @return self Returns itself
229 | */
230 | public function printUsage(): self {
231 | $this->log->info(
232 | 'Usage: %1$s %2$s [OPTIONS] PATH
233 |
234 | %3$s
235 |
236 | Add new server "host.example.net":
237 |
238 | %1$s %2$s server/host.example.net
239 |
240 | Add model for server "host.example.net":
241 |
242 | %1$s %2$s server/host.example.net/model --manufacturer VendorXY --title Model123
243 | %1$s %2$s host.example.net/model --manufacturer VendorXY --title Model123
244 |
245 | Add model to all servers:
246 |
247 | %1$s %2$s server/*/model --manufacturer VendorXY --title Model123',
248 | $this->config['composer']['extra']['name'],
249 | $this->getName(),
250 | $this->getDescription()
251 | );
252 |
253 | return $this;
254 | }
255 |
256 | }
257 |
--------------------------------------------------------------------------------
/src/Command/Dummy.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-19 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 |
31 | /**
32 | * Command "dummy"
33 | */
34 | class Dummy extends Command {
35 |
36 | /**
37 | * Execute command
38 | *
39 | * @return self Returns itself
40 | *
41 | * @throws Exception on error
42 | */
43 | public function execute(): self {
44 | $this->log
45 | ->printAsMessage()
46 | ->info('Hello, world!')
47 | ->printEmptyLine();
48 |
49 | return $this;
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/Command/Init.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 | use bheisig\cli\Command\Init as BaseInit;
31 |
32 | /**
33 | * Command "init"
34 | */
35 | class Init extends Command {
36 |
37 | /**
38 | * Process some routines before executing command
39 | *
40 | * @return self Returns itself
41 | *
42 | * @throws Exception on error
43 | */
44 | public function setup(): Command {
45 | // Restore basic setup to skip validation of configuration settings:
46 | $this->start = time();
47 |
48 | return $this;
49 | }
50 |
51 | /**
52 | * Execute command
53 | *
54 | * @return self Returns itself
55 | *
56 | * @throws Exception on error
57 | */
58 | public function execute(): self {
59 | $this->log
60 | ->printAsMessage()
61 | ->info($this->getDescription())
62 | ->printEmptyLine()
63 | ->printAsOutput();
64 |
65 | try {
66 | $baseInit = new BaseInit($this->config, $this->log);
67 | $baseInit->execute();
68 |
69 | $this->log
70 | ->info('Create cache files needed for faster processing:')
71 | ->printEmptyLine()
72 | ->info(
73 | ' %s cache',
74 | $this->config['composer']['extra']['name']
75 | );
76 | } catch (Exception $e) {
77 | $code = $e->getCode();
78 |
79 | if ($code === 0) {
80 | $code = 500;
81 | }
82 |
83 | throw new Exception(
84 | sprintf(
85 | 'Unable to initiate "%s": ' . $e->getMessage(),
86 | $this->config['composer']['extra']['name']
87 | ),
88 | $code
89 | );
90 | }
91 |
92 | return $this;
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/Command/Log.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 | use \BadMethodCallException;
31 | use \RuntimeException;
32 |
33 | /**
34 | * Command "log"
35 | */
36 | class Log extends Command {
37 |
38 | protected $objectID;
39 | protected $objectTitle;
40 | protected $message;
41 |
42 | /**
43 | * Execute command
44 | *
45 | * @return self Returns itself
46 | *
47 | * @throws Exception on error
48 | */
49 | public function execute(): self {
50 | $this->log
51 | ->printAsMessage()
52 | ->info($this->getDescription())
53 | ->printEmptyLine();
54 |
55 | switch (count($this->config['arguments'])) {
56 | case 0:
57 | break;
58 | case 1:
59 | $object = $this->useIdoitAPI()->identifyObject(
60 | $this->config['arguments'][0]
61 | );
62 |
63 | $this->objectID = (int) $object['id'];
64 | $this->objectTitle = $object['title'];
65 | break;
66 | default:
67 | throw new BadMethodCallException(
68 | 'Too many arguments; please provide only one object title or numeric identifier'
69 | );
70 | }
71 |
72 | $this->identifyMessage($this->config['options']);
73 |
74 | if (!$this->hasObject()) {
75 | if ($this->useUserInteraction()->isInteractive() === false) {
76 | throw new BadMethodCallException(
77 | 'No object, no log'
78 | );
79 | }
80 |
81 | $object = $this->askForObject();
82 | $this->objectID = (int) $object['id'];
83 | $this->objectTitle = $object['title'];
84 | }
85 |
86 | $this->reportObject();
87 |
88 | if (!$this->hasMessage()) {
89 | if ($this->useUserInteraction()->isInteractive() === false) {
90 | throw new BadMethodCallException(
91 | 'No message, no log'
92 | );
93 | }
94 |
95 | $this->askForMessage();
96 | }
97 |
98 | $this->reportMessage();
99 |
100 | $this->save();
101 |
102 | return $this;
103 | }
104 |
105 | protected function identifyMessage(array $options): self {
106 | $message = '';
107 |
108 | if (array_key_exists('m', $options)) {
109 | if (is_array($options['m'])) {
110 | throw new BadMethodCallException(
111 | 'Use option -m only once'
112 | );
113 | }
114 |
115 | $message = $options['m'];
116 | }
117 |
118 | if (array_key_exists('message', $options)) {
119 | if (isset($message)) {
120 | throw new BadMethodCallException(
121 | 'Use either option -m or --message, not both'
122 | );
123 | }
124 |
125 | if (is_array($options['message'])) {
126 | throw new BadMethodCallException(
127 | 'Use option --message only once'
128 | );
129 | }
130 |
131 | $message = $options['message'];
132 | }
133 |
134 | if (strlen($message) > 0) {
135 | $this->message = $message;
136 | }
137 |
138 | return $this;
139 | }
140 |
141 | protected function hasObject(): bool {
142 | return isset($this->objectID);
143 | }
144 |
145 | protected function hasMessage(): bool {
146 | return isset($this->message);
147 | }
148 |
149 | /**
150 | * @return self Returns itself
151 | *
152 | * @throws Exception on error
153 | */
154 | protected function askForMessage(): self {
155 | try {
156 | if (function_exists('exec') === false) {
157 | throw new RuntimeException(
158 | 'Executing external commands via shell is not allowed; probably function "exec" is disabled'
159 | );
160 | }
161 |
162 | $editor = $this->findEditor();
163 |
164 | if ($editor === '') {
165 | // Last chance:
166 | $editor = $this->askForEditor();
167 | }
168 |
169 | $this->log->debug('Editor: %s', $editor);
170 |
171 | $tmpFile = $temp_file = tempnam(
172 | sys_get_temp_dir(),
173 | $this->config['composer']['extra']['name'] . '_'
174 | );
175 |
176 | if (!is_string($tmpFile) || !is_writable($tmpFile)) {
177 | throw new RuntimeException(sprintf(
178 | 'Permission denied to write file "%s"',
179 | $tmpFile
180 | ));
181 | }
182 |
183 | $objectTitle = $this->objectTitle;
184 | $objectID = $this->objectID;
185 |
186 | $status = file_put_contents(
187 | $tmpFile,
188 | << `tty`", $output, $exitCode);
209 |
210 | if ($exitCode > 0) {
211 | foreach ($output as $line) {
212 | $this->log->warning($line);
213 | }
214 |
215 | throw new RuntimeException(sprintf(
216 | 'Editor closed with exit code %s',
217 | $exitCode
218 | ));
219 | }
220 |
221 | $content = file_get_contents($tmpFile);
222 |
223 | if ($content === false) {
224 | throw new RuntimeException(sprintf(
225 | 'Unable to read from temporary file "%s"',
226 | $tmpFile
227 | ));
228 | }
229 |
230 | $status = unlink($tmpFile);
231 |
232 | if ($status === false) {
233 | throw new RuntimeException(sprintf(
234 | 'Unable to remove temporary file "%s"',
235 | $tmpFile
236 | ));
237 | }
238 |
239 | $lines = explode(PHP_EOL, $content);
240 |
241 | $messageLines = [];
242 |
243 | foreach ($lines as $line) {
244 | if (strpos($line, '#') === 0) {
245 | continue;
246 | }
247 |
248 | $messageLines[] = $line;
249 | }
250 |
251 | $this->message = trim(implode(PHP_EOL, $messageLines));
252 |
253 | if (strlen($this->message) === 0) {
254 | throw new RuntimeException(
255 | 'Your message is empty'
256 | );
257 | }
258 | } catch (Exception $e) {
259 | throw new RuntimeException(sprintf(
260 | 'Asking for a message resulted in an error: %s',
261 | $e->getMessage()
262 | ));
263 | }
264 |
265 | return $this;
266 | }
267 |
268 | /**
269 | * Find a proper editor to edit files
270 | *
271 | * Try (in the following order):
272 | * - Environment variable "EDITOR"
273 | * - Environment variable "VISUAL"
274 | * - Command "editor"
275 | * - Command "sensible-editor"
276 | * - Command "xdg-mime query default"
277 | * - Editor "nano"
278 | * - Editor "vim"
279 | * - Editor "vi"
280 | * - Editor "joe"
281 | * - Editor "gedit"
282 | * - Editor "kate"
283 | *
284 | * @return string
285 | */
286 | protected function findEditor(): string {
287 | $environmentVariables = [
288 | 'EDITOR',
289 | 'VISUAL'
290 | ];
291 |
292 | foreach ($environmentVariables as $environmentVariable) {
293 | $this->log->debug('Check environment variable "%s"', $environmentVariable);
294 |
295 | $editor = getenv($environmentVariable);
296 |
297 | if ($editor !== false && strlen($editor) > 0) {
298 | return $editor;
299 | }
300 | }
301 |
302 | $commands = [
303 | 'editor',
304 | 'sensible-editor',
305 | 'xdg-mime query default',
306 | 'nano',
307 | 'vim',
308 | 'vi',
309 | 'joe',
310 | 'gedit',
311 | 'kate'
312 | ];
313 |
314 | foreach ($commands as $command) {
315 | $this->log->debug('Check command "%s"', $command);
316 |
317 | if ($this->testCommand($command)) {
318 | return $command;
319 | }
320 | }
321 |
322 | $this->log->debug('No editor found');
323 |
324 | return '';
325 | }
326 |
327 | protected function testCommand(string $command): bool {
328 | $output = [];
329 | $exitCode = 0;
330 |
331 | exec("type $command > /dev/null 2> /dev/null", $output, $exitCode);
332 |
333 | if ($exitCode === 0) {
334 | return true;
335 | }
336 |
337 | return false;
338 | }
339 |
340 | /**
341 | * @return string
342 | */
343 | protected function askForEditor(): string {
344 | $editor = $this->useUserInteraction()->askQuestion('Which editor do you prefer?');
345 |
346 | if (strlen($editor) === 0) {
347 | $this->log->warning('Excuse me, what do you mean?');
348 |
349 | return $this->askForEditor();
350 | }
351 |
352 | if ($this->testCommand($editor) === false) {
353 | $this->log->warning('Command not found');
354 |
355 | return $this->askForEditor();
356 | }
357 |
358 | return $editor;
359 | }
360 |
361 | protected function reportObject(): self {
362 | $this->log->debug(
363 | 'Object identified: %s [%s]',
364 | $this->objectTitle,
365 | $this->objectID
366 | );
367 |
368 | return $this;
369 | }
370 |
371 | protected function reportMessage(): self {
372 | $this->log->debug(
373 | 'Message:',
374 | $this->message
375 | );
376 |
377 | $lines = explode(PHP_EOL, $this->message);
378 |
379 | foreach ($lines as $line) {
380 | $this->log->debug($line);
381 | }
382 |
383 | return $this;
384 | }
385 |
386 | /**
387 | * @return self Returns itself
388 | *
389 | * @throws Exception on error
390 | */
391 | protected function save(): self {
392 | $this->log->debug('Save…');
393 |
394 | $this->useIdoitAPIFactory()->getCMDBLogbook()->create(
395 | $this->objectID,
396 | $this->message
397 | );
398 |
399 | return $this;
400 | }
401 |
402 | /**
403 | * Print usage of command
404 | *
405 | * @return self Returns itself
406 | */
407 | public function printUsage(): self {
408 | $editor = $this->findEditor();
409 |
410 | if ($editor === '') {
411 | $editor = 'no editor found';
412 | }
413 |
414 | $this->log->info(
415 | <<< EOF
416 | %3\$s
417 |
418 | USAGE
419 | \$ %1\$s %2\$s [OPTIONS] [OBJECT]
420 |
421 | ARGUMENTS
422 | OBJECT Object title or numeric identifier
423 |
424 | COMMAND OPTIONS
425 | -m MESSAGE, Add message text, otherwise type your message in
426 | --message=MESSAGE your prefered editor: %4\$s
427 |
428 | COMMON OPTIONS
429 | -c FILE, Include settings stored in a JSON-formatted
430 | --config=FILE configuration file FILE; repeat option for more
431 | than one FILE
432 | -s KEY=VALUE, Add runtime setting KEY with its VALUE; separate
433 | --setting=KEY=VALUE nested keys with ".", for example "key1.key2=123";
434 | repeat option for more than one KEY
435 |
436 | --no-colors Do not print colored messages
437 | -q, --quiet Do not output messages, only errors
438 | -v, --verbose Be more verbose
439 |
440 | -h, --help Print this help or information about a
441 | specific command
442 | --version Print version information
443 |
444 | -y, --yes No user interaction required; answer questions
445 | automatically with default values
446 |
447 | EXAMPLES
448 | # Add entry to object identified by its title:
449 | \$ %1\$s %2\$s host01.example.com -m "Reboot server for Kernel updates"
450 |
451 | # …or by its numeric identifier:
452 | \$ %1\$s %2\$s 42 -m "Reboot server for Kernel updates"
453 |
454 | # If argument OBJECT or message is omitted you'll be asked for it:
455 | \$ %1\$s %2\$s
456 | Object? host01.example.com
457 | # Based on environment variable EDITOR
458 | # type your message in your prefered editor…
459 | EOF
460 | ,
461 | $this->config['composer']['extra']['name'],
462 | $this->getName(),
463 | $this->getDescription(),
464 | $editor
465 | );
466 |
467 | return $this;
468 | }
469 |
470 | }
471 |
--------------------------------------------------------------------------------
/src/Command/Logs.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 | use \BadMethodCallException;
31 |
32 | /**
33 | * Command "logs"
34 | */
35 | class Logs extends Command {
36 |
37 | /**
38 | * @var array
39 | */
40 | protected $filterByIDs = [];
41 |
42 | /**
43 | * @var array
44 | */
45 | protected $filterByTitles = [];
46 |
47 | /**
48 | * @var string
49 | */
50 | protected $filterByDateTime = '';
51 |
52 | /**
53 | * @var int
54 | */
55 | protected $limit = 0;
56 |
57 | /**
58 | * @var int
59 | */
60 | protected $hardLimit = 10000;
61 |
62 | /**
63 | * @var array
64 | */
65 | protected $events = [
66 | 'C__LOGBOOK_EVENT__OBJECTTYPE_PURGED' => 'Object type purged',
67 | 'C__LOGBOOK_EVENT__OBJECTTYPE_ARCHIVED' => 'Object type archived',
68 | 'C__LOGBOOK_EVENT__OBJECTTYPE_RECYCLED' => 'Object type recycled',
69 | 'C__LOGBOOK_EVENT__OBJECTTYPE_CHANGED' => 'Object type updated',
70 | 'C__LOGBOOK_EVENT__OBJECTTYPE_CREATED' => 'Object type created',
71 | 'C__LOGBOOK_EVENT__OBJECT_PURGED' => 'Object purged',
72 | 'C__LOGBOOK_EVENT__OBJECT_ARCHIVED' => 'Object archived',
73 | 'C__LOGBOOK_EVENT__OBJECT_RECYCLED' => 'Object recycled',
74 | 'C__LOGBOOK_EVENT__OBJECT_CHANGED' => 'Object updated',
75 | 'C__LOGBOOK_EVENT__OBJECT_CREATED' => 'Object created',
76 | 'C__LOGBOOK_EVENT__OBJECTTYPE_PURGED__NOT' => 'Object type purged… not',
77 | 'C__LOGBOOK_EVENT__OBJECTTYPE_ARCHIVED__NOT' => 'Object type archived… not',
78 | 'C__LOGBOOK_EVENT__OBJECTTYPE_RECYCLED__NOT' => 'Object type recycled… not',
79 | 'C__LOGBOOK_EVENT__OBJECTTYPE_CHANGED__NOT' => 'Object type updated… not',
80 | 'C__LOGBOOK_EVENT__OBJECTTYPE_CREATED__NOT' => 'Object type created… not',
81 | 'C__LOGBOOK_EVENT__OBJECT_PURGED__NOT' => 'Object purged… not',
82 | 'C__LOGBOOK_EVENT__OBJECT_ARCHIVED__NOT' => 'Object archived… not',
83 | 'C__LOGBOOK_EVENT__OBJECT_RECYCLED__NOT' => 'Object recycled… not',
84 | 'C__LOGBOOK_EVENT__OBJECT_CHANGED__NOT' => 'Object updated… not',
85 | 'C__LOGBOOK_EVENT__OBJECT_CREATED__NOT' => 'Object created… not',
86 | // @todo What are these events?
87 | // 'C__LOGBOOK_EVENT__POBJECT_FEMALE_SOCKET_CREATED__NOT' => '',
88 | // 'C__LOGBOOK_EVENT__POBJECT_MALE_PLUG_CREATED__NOT' => '',
89 | 'C__LOGBOOK_EVENT__CATEGORY_PURGED' => 'Category entry purged',
90 | 'C__LOGBOOK_EVENT__CATEGORY_CHANGED' => 'Category entry updated',
91 | 'C__LOGBOOK_EVENT__CATEGORY_ARCHIVED' => 'Category entry archvied',
92 | 'C__LOGBOOK_EVENT__CATEGORY_DELETED' => 'Category entry deleted'
93 | ];
94 |
95 | /**
96 | * @var array
97 | */
98 | protected $logs = [];
99 |
100 | /**
101 | * Execute command
102 | *
103 | * @return self Returns itself
104 | *
105 | * @throws Exception on error
106 | */
107 | public function execute(): self {
108 | $this->log
109 | ->printAsMessage()
110 | ->info($this->getDescription())
111 | ->printEmptyLine();
112 |
113 | $this
114 | ->buildFilters($this->config['options'])
115 | ->fetchObjectIDsByTitles()
116 | ->readLogs()
117 | ->printLogs();
118 |
119 | if ($this->follow()) {
120 | $this->keepFollowing();
121 | }
122 |
123 | return $this;
124 | }
125 |
126 | /**
127 | * @param array $options
128 | *
129 | * @return self Returns itself
130 | *
131 | * @throws Exception on error
132 | */
133 | protected function buildFilters(array $options): self {
134 | return $this
135 | ->buildIDFilter($options)
136 | ->buildTitleFilter($options)
137 | ->buildLimitFilter($options)
138 | ->buildDateTimeFilter($options);
139 | }
140 |
141 | /**
142 | * @param array $options
143 | *
144 | * @return self Returns itself
145 | *
146 | * @throws Exception on error
147 | */
148 | protected function buildIDFilter(array $options): self {
149 | if (!array_key_exists('id', $options)) {
150 | return $this;
151 | }
152 |
153 | $ids = [];
154 |
155 | if (is_array($options['id'])) {
156 | $ids = $options['id'];
157 | } elseif (is_string($options['id'])) {
158 | $ids[] = $options['id'];
159 | }
160 |
161 | foreach ($ids as $id) {
162 | if ($this->useValidate()->isIDAsString($id) === false) {
163 | throw new BadMethodCallException(sprintf(
164 | 'Invalid filter by id: %s',
165 | $id
166 | ));
167 | }
168 |
169 | $this->filterByIDs[] = (int) $id;
170 | }
171 |
172 | return $this;
173 | }
174 |
175 | /**
176 | * @param array $options
177 | *
178 | * @return self Returns itself
179 | *
180 | * @throws Exception on error
181 | */
182 | protected function buildTitleFilter(array $options): self {
183 | if (!array_key_exists('title', $options)) {
184 | return $this;
185 | }
186 |
187 | $titles = [];
188 |
189 | if (is_array($options['title'])) {
190 | $titles = $options['title'];
191 | } elseif (is_string($options['title'])) {
192 | $titles[] = $options['title'];
193 | }
194 |
195 | foreach ($titles as $title) {
196 | if ($this->useValidate()->isOneLiner($title) === false) {
197 | throw new BadMethodCallException(sprintf(
198 | 'Invalid filter by title: %s',
199 | $title
200 | ));
201 | }
202 |
203 | $this->filterByTitles[] = $title;
204 | }
205 |
206 | return $this;
207 | }
208 |
209 | /**
210 | * @param array $options
211 | *
212 | * @return self Returns itself
213 | *
214 | * @throws Exception on error
215 | */
216 | protected function buildLimitFilter(array $options): self {
217 | if (array_key_exists('n', $options)) {
218 | if (is_array($options['n'])) {
219 | throw new BadMethodCallException(
220 | 'Use option -n only once'
221 | );
222 | }
223 |
224 | if (!is_numeric($options['n']) ||
225 | (int) $options['n'] <= 0) {
226 | throw new BadMethodCallException(
227 | 'Option -n is not a positive integer'
228 | );
229 | }
230 |
231 | $this->limit = (int) $options['n'];
232 | }
233 |
234 | if (array_key_exists('number', $options)) {
235 | if ($this->limit > 0) {
236 | throw new BadMethodCallException(
237 | 'Use either option -n or --number, not both'
238 | );
239 | }
240 |
241 | if (is_array($options['number'])) {
242 | throw new BadMethodCallException(
243 | 'Use option --number only once'
244 | );
245 | }
246 |
247 | if (!is_numeric($options['number']) ||
248 | (int) $options['number'] <= 0) {
249 | throw new BadMethodCallException(
250 | 'Option --number is not a positive integer'
251 | );
252 | }
253 |
254 | $this->limit = (int) $options['number'];
255 | }
256 |
257 | return $this;
258 | }
259 |
260 | /**
261 | * @param array $options
262 | *
263 | * @return self Returns itself
264 | *
265 | * @throws Exception on error
266 | */
267 | protected function buildDateTimeFilter(array $options): self {
268 | if (!array_key_exists('since', $options)) {
269 | return $this;
270 | }
271 |
272 | if (is_array($options['since'])) {
273 | throw new BadMethodCallException(
274 | 'Use option --since only once'
275 | );
276 | }
277 |
278 | if (strtotime($options['since']) === false) {
279 | throw new BadMethodCallException(
280 | 'Option --since is not a valid date/time'
281 | );
282 | }
283 |
284 | $this->filterByDateTime = $options['since'];
285 |
286 | return $this;
287 | }
288 |
289 | protected function hasIDFilter(): bool {
290 | return count($this->filterByIDs) > 0;
291 | }
292 |
293 | protected function hasTitleFilter(): bool {
294 | return count($this->filterByTitles) > 0;
295 | }
296 |
297 | protected function hasDateTimeFilter(): bool {
298 | return strlen($this->filterByDateTime) > 0;
299 | }
300 |
301 | protected function hasAnyFilter(): bool {
302 | return $this->hasIDFilter() ||
303 | $this->hasTitleFilter() ||
304 | $this->hasDateTimeFilter();
305 | }
306 |
307 | protected function isLimited(): bool {
308 | return $this->limit > 0;
309 | }
310 |
311 | protected function follow(): bool {
312 | if (array_key_exists('f', $this->config['options']) ||
313 | array_key_exists('follow', $this->config['options'])) {
314 | return true;
315 | }
316 |
317 | return false;
318 | }
319 |
320 | /**
321 | * @return self Returns itself
322 | *
323 | * @throws Exception on error
324 | */
325 | protected function fetchObjectIDsByTitles(): self {
326 | if ($this->hasTitleFilter()) {
327 | $objectIDs = $this->useIdoitAPI()->fetchObjectIDsByTitles($this->filterByTitles);
328 |
329 | $this->filterByIDs = array_merge($this->filterByIDs, $objectIDs);
330 | }
331 | return $this;
332 | }
333 |
334 | /**
335 | * @return self Returns itself
336 | *
337 | * @throws Exception on error
338 | */
339 | protected function readLogs(): self {
340 | $this->logs = [];
341 |
342 | if (!$this->hasAnyFilter()) {
343 | $this->logs = $this->useIdoitAPIFactory()->getCMDBLogbook()->read(null, $this->hardLimit);
344 | } else {
345 | $since = null;
346 |
347 | if ($this->hasDateTimeFilter()) {
348 | $since = $this->filterByDateTime;
349 | }
350 |
351 | if ($this->hasIDFilter()) {
352 | foreach ($this->filterByIDs as $objectID) {
353 | $this->logs = array_merge(
354 | $this->logs,
355 | $this->useIdoitAPIFactory()->getCMDBLogbook()->readByObject(
356 | $objectID,
357 | $since,
358 | $this->hardLimit
359 | )
360 | );
361 | }
362 |
363 | usort(
364 | $this->logs,
365 | [self::class, 'sortLogsByDate']
366 | );
367 | } else {
368 | $this->logs = $this->useIdoitAPIFactory()->getCMDBLogbook()->read(
369 | $since,
370 | $this->hardLimit
371 | );
372 | }
373 | }
374 |
375 | if ($this->isLimited()) {
376 | $this->logs = array_slice(
377 | $this->logs,
378 | -$this->limit
379 | );
380 | }
381 |
382 | return $this;
383 | }
384 |
385 | /**
386 | * @return self Returns itself
387 | *
388 | * @throws Exception on error
389 | */
390 | protected function printLogs(): self {
391 | switch (count($this->logs)) {
392 | case 0:
393 | $this->log->printAsMessage()->debug(
394 | 'No log events'
395 | );
396 | break;
397 | case 1:
398 | $this->log->printAsMessage()->debug(
399 | '1 log event:'
400 | );
401 | break;
402 | default:
403 | $this->log->printAsMessage()->debug(
404 | '%s log events:',
405 | count($this->logs)
406 | );
407 | break;
408 | }
409 |
410 | foreach ($this->logs as $log) {
411 | $this->printLog($log);
412 | }
413 |
414 | return $this;
415 | }
416 |
417 | /**
418 | * Sort logs by date
419 | *
420 | * @param array $logA
421 | * @param array $logB
422 | *
423 | * @return int
424 | */
425 | public static function sortLogsByDate(array $logA, $logB): int {
426 | if ($logA['date'] === $logB['date']) {
427 | return 0;
428 | }
429 |
430 | $logATimeStamp = strtotime($logA['date']);
431 | $logBTimeStamp = strtotime($logB['date']);
432 |
433 | return $logATimeStamp < $logBTimeStamp ? -1 : 1;
434 | }
435 |
436 | protected function printLog(array $log) {
437 | $id = $log['logbook_id'];
438 | $timestamp = $log['date'];
439 | $username = $log['username'];
440 | $objectTitle = $log['object_title'];
441 | $objectID = $log['object_id'];
442 |
443 | if (array_key_exists($log['event'], $this->events)) {
444 | $event = $this->events[$log['event']];
445 | } else {
446 | $event = $log['event'];
447 | }
448 |
449 | $this->log
450 | ->printAsOutput()
451 | ->info(
452 | <<< EOF
453 | #$id $username @ $timestamp
454 | $objectTitle [$objectID]
455 | $event
456 |
457 | EOF
458 | );
459 | }
460 |
461 | /**
462 | * @throws Exception on error
463 | */
464 | protected function keepFollowing() {
465 | $this->filterByDateTime = date('c');
466 |
467 | if (count($this->logs) > 0) {
468 | $lastLog = end($this->logs);
469 | $timestamp = strtotime($lastLog['date']) + 1;
470 | $this->filterByDateTime = date('c', $timestamp);
471 | }
472 |
473 | while (true) {
474 | $this
475 | ->readLogs()
476 | ->printLogs();
477 |
478 | if (count($this->logs) > 0) {
479 | $lastLog = end($this->logs);
480 | $timestamp = strtotime($lastLog['date']) + 1;
481 | $this->filterByDateTime = date('c', $timestamp);
482 | }
483 |
484 | sleep(2);
485 | }
486 | }
487 |
488 | /**
489 | * Print usage of command
490 | *
491 | * @return self Returns itself
492 | */
493 | public function printUsage(): self {
494 | $this->log->info(
495 | <<< EOF
496 | %3\$s
497 |
498 | USAGE
499 | \$ %1\$s %2\$s [OPTIONS]
500 |
501 | COMMAND OPTIONS
502 | -f, --follow Start a continuous stream of logs
503 | --id=ID Filter logs by numeric object identifier
504 | Repeat to filter by more than one identifiers
505 | -n LIMIT, Limit to last number of logs
506 | --number=LIMIT Note: There is a hard limit set to a maximum amount of
507 | %4\$s entries to prevent server-side errors
508 | --since=TIME Filter logs since a specific date/time
509 | May be everything that can be interpreted as a date/time
510 | --title=TITLE Filter logs by object title
511 | Repeat to filter by more than one titles
512 |
513 | Any combination of options is allowed.
514 |
515 | COMMON OPTIONS
516 | -c FILE, Include settings stored in a JSON-formatted
517 | --config=FILE configuration file FILE; repeat option for more
518 | than one FILE
519 | -s KEY=VALUE, Add runtime setting KEY with its VALUE; separate
520 | --setting=KEY=VALUE nested keys with ".", for example "key1.key2=123";
521 | repeat option for more than one KEY
522 |
523 | --no-colors Do not print colored messages
524 | -q, --quiet Do not output messages, only errors
525 | -v, --verbose Be more verbose
526 |
527 | -h, --help Print this help or information about a
528 | specific command
529 | --version Print version information
530 |
531 | -y, --yes No user interaction required; answer questions
532 | automatically with default values
533 |
534 | EXAMPLES
535 | # Read all logs at once:
536 | \$ %1\$s %2\$s
537 |
538 | # Read logs about 2 objects by their identifiers:
539 | \$ %1\$s %2\$s --id 23 --id 42
540 |
541 | # Read logs about an objects by its title:
542 | \$ %1\$s %2\$s --title "host001.example.com"
543 |
544 | # Follow:
545 | \$ %1\$s %2\$s -f
546 |
547 | # Print only logs since today:
548 | \$ %1\$s %2\$s --since today
549 |
550 | # Or since a specific date:
551 | \$ %1\$s %2\$s --since 2018-01-01
552 | EOF
553 | ,
554 | $this->config['composer']['extra']['name'],
555 | $this->getName(),
556 | $this->getDescription(),
557 | $this->hardLimit
558 | );
559 |
560 | return $this;
561 | }
562 |
563 | }
564 |
--------------------------------------------------------------------------------
/src/Command/NextIP.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 | use \BadMethodCallException;
31 |
32 | /**
33 | * Command "nextip"
34 | */
35 | class NextIP extends Command {
36 |
37 | protected $freeIPAddresses = [];
38 |
39 | /**
40 | * Execute command
41 | *
42 | * @return self Returns itself
43 | *
44 | * @throws Exception on error
45 | */
46 | public function execute(): self {
47 | $this->log
48 | ->printAsMessage()
49 | ->info($this->getDescription())
50 | ->printEmptyLine();
51 |
52 | switch (count($this->config['arguments'])) {
53 | case 0:
54 | if ($this->useUserInteraction()->isInteractive() === false) {
55 | throw new BadMethodCallException(
56 | 'No object, no IP'
57 | );
58 | }
59 |
60 | $object = $this->askForObject();
61 | $objectID = (int) $object['id'];
62 | break;
63 | case 1:
64 | $object = $this->useIdoitAPI()->identifyObject(
65 | $this->config['arguments'][0]
66 | );
67 |
68 | $objectID = (int) $object['id'];
69 | break;
70 | default:
71 | throw new BadMethodCallException(
72 | 'Too many arguments; please provide only one object title or numeric identifier'
73 | );
74 | }
75 |
76 | $next = $this->useIdoitAPIFactory()->getSubnet()->load($objectID)->next();
77 |
78 | $this->log
79 | ->printAsOutput()
80 | ->info($next);
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * Print usage of command
87 | *
88 | * @return self Returns itself
89 | */
90 | public function printUsage(): self {
91 | $this->log->info(
92 | <<< EOF
93 | %3\$s
94 |
95 | USAGE
96 | \$ %1\$s %2\$s [OPTIONS] [SUBNET]
97 |
98 | ARGUMENTS
99 | SUBNET Object title or numeric identifier
100 | Must be a "layer-3-net" object
101 | Only works for IPv4 subnets
102 |
103 | COMMON OPTIONS
104 | -c FILE, Include settings stored in a JSON-formatted
105 | --config=FILE configuration file FILE; repeat option for more
106 | than one FILE
107 | -s KEY=VALUE, Add runtime setting KEY with its VALUE; separate
108 | --setting=KEY=VALUE nested keys with ".", for example "key1.key2=123";
109 | repeat option for more than one KEY
110 |
111 | --no-colors Do not print colored messages
112 | -q, --quiet Do not output messages, only errors
113 | -v, --verbose Be more verbose
114 |
115 | -h, --help Print this help or information about a
116 | specific command
117 | --version Print version information
118 |
119 | -y, --yes No user interaction required; answer questions
120 | automatically with default values
121 |
122 | EXAMPLES
123 | # Print next IPv4 for subnet by its title:
124 | \$ %1\$s %2\$s "Global v4"
125 | \$ %1\$s %2\$s Global\\ v4
126 |
127 | # …or by its numeric identifier:
128 | \$ %1\$s %2\$s 20
129 |
130 | # If argument SUBNET is omitted you'll be asked for it:
131 | \$ %1\$s %2\$s
132 | Object? Global v4
133 | EOF
134 | ,
135 | $this->config['composer']['extra']['name'],
136 | $this->getName(),
137 | $this->getDescription()
138 | );
139 |
140 | return $this;
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/src/Command/Search.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 | use \BadMethodCallException;
31 |
32 | /**
33 | * Command "search"
34 | */
35 | class Search extends Command {
36 |
37 | /**
38 | * Execute command
39 | *
40 | * @return self Returns itself
41 | *
42 | * @throws Exception on error
43 | */
44 | public function execute(): self {
45 | $this->log
46 | ->printAsMessage()
47 | ->info($this->getDescription())
48 | ->printEmptyLine();
49 |
50 | switch (count($this->config['arguments'])) {
51 | case 0:
52 | if ($this->useUserInteraction()->isInteractive() === false) {
53 | throw new BadMethodCallException(
54 | 'No query, no search'
55 | );
56 | }
57 |
58 | $query = $this->useUserInteraction()->askQuestion('Query?');
59 | break;
60 | case 1:
61 | $query = $this->config['arguments'][0];
62 | break;
63 | default:
64 | throw new BadMethodCallException(
65 | 'Too many arguments; please provide only one query'
66 | );
67 | }
68 |
69 | if ($query === '') {
70 | throw new BadMethodCallException(
71 | 'Query is required.'
72 | );
73 | }
74 |
75 | $results = $this->useIdoitAPIFactory()->getCMDB()->search($query);
76 |
77 | switch (count($results)) {
78 | case 0:
79 | $this->log
80 | ->printAsMessage()
81 | ->info('Nothing found');
82 | break;
83 | case 1:
84 | $this->log
85 | ->printAsMessage()
86 | ->info('Found 1 result');
87 | break;
88 | default:
89 | $this->log
90 | ->printAsMessage()
91 | ->info('Found %s results', count($results));
92 | break;
93 | }
94 |
95 | $baseLink = strstr($this->config['api']['url'], '/src/jsonrpc.php', true);
96 |
97 | foreach ($results as $result) {
98 | $this->log
99 | ->printAsOutput()
100 | ->printEmptyLine()
101 | ->info('%s', trim($result['value']))
102 | ->info('Source: %s [%s]', $result['key'], $result['type'])
103 | ->info('Link: %s', $baseLink . $result['link']);
104 | }
105 |
106 | return $this;
107 | }
108 |
109 | /**
110 | * Print usage of command
111 | *
112 | * @return self Returns itself
113 | */
114 | public function printUsage(): self {
115 | $this->log->info(
116 | <<< EOF
117 | %3\$s
118 |
119 | USAGE
120 | \$ %1\$s %2\$s [OPTIONS] QUERY
121 |
122 | ARGUMENTS
123 | QUERY What are you looking for?
124 |
125 | COMMON OPTIONS
126 | -c FILE, Include settings stored in a JSON-formatted
127 | --config=FILE configuration file FILE; repeat option for more
128 | than one FILE
129 | -s KEY=VALUE, Add runtime setting KEY with its VALUE; separate
130 | --setting=KEY=VALUE nested keys with ".", for example "key1.key2=123";
131 | repeat option for more than one KEY
132 |
133 | --no-colors Do not print colored messages
134 | -q, --quiet Do not output messages, only errors
135 | -v, --verbose Be more verbose
136 |
137 | -h, --help Print this help or information about a
138 | specific command
139 | --version Print version information
140 |
141 | -y, --yes No user interaction required; answer questions
142 | automatically with default values
143 |
144 | EXAMPLES
145 | \$ %1\$s %2\$s mylittleserver
146 | \$ %1\$s %2\$s "My little server"
147 | \$ %1\$s %2\$s My\\ little\\ server
148 | \$ %1\$s %2\$s 10.42.23.1
149 | EOF
150 | ,
151 | $this->config['composer']['extra']['name'],
152 | $this->getName(),
153 | $this->getDescription()
154 | );
155 |
156 | return $this;
157 | }
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/src/Command/Show.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 | use \BadMethodCallException;
31 |
32 | /**
33 | * Command "show"
34 | */
35 | class Show extends Command {
36 |
37 | /**
38 | * Execute command
39 | *
40 | * @return self Returns itself
41 | *
42 | * @throws Exception on error
43 | */
44 | public function execute(): self {
45 | $this->log
46 | ->printAsMessage()
47 | ->info($this->getDescription())
48 | ->printEmptyLine();
49 |
50 | $object = $this->loadObject($this->config['arguments']);
51 |
52 | $this->printOutput($object);
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * Load object based on CLI argument
59 | *
60 | * @param array $arguments CLI arguments
61 | *
62 | * @return array Everything about the object
63 | *
64 | * @throws Exception on error
65 | */
66 | protected function loadObject(array $arguments): array {
67 | switch (count($arguments)) {
68 | case 0:
69 | if ($this->useUserInteraction()->isInteractive() === false) {
70 | throw new BadMethodCallException(
71 | 'No object, no output'
72 | );
73 | }
74 |
75 | $object = $this->askForObject();
76 |
77 | $this->log->debug('Loading…');
78 |
79 | return $this
80 | ->useIdoitAPIFactory()
81 | ->getCMDBObject()
82 | ->load((int) $object['id']);
83 | case 1:
84 | if (is_numeric($arguments[0])) {
85 | $objectID = (int) $arguments[0];
86 |
87 | if ($objectID <= 0) {
88 | throw new Exception(
89 | 'Invalid object. Please specify an numeric identifier'
90 | );
91 | }
92 |
93 | $this->log->debug('Loading…');
94 |
95 | return $this
96 | ->useIdoitAPIFactory()
97 | ->getCMDBObject()
98 | ->load($objectID);
99 | } else {
100 | $objects = $this
101 | ->useIdoitAPIFactory()
102 | ->getCMDBObjects()
103 | ->read(['title' => $arguments[0]]);
104 |
105 | switch (count($objects)) {
106 | case 0:
107 | if ($this->useUserInteraction()->isInteractive() === false) {
108 | throw new BadMethodCallException(
109 | 'Object not found'
110 | );
111 | }
112 |
113 | $this->log->warning('Object not found');
114 | $object = $this->askForObject();
115 |
116 | $this->log->debug('Loading…');
117 |
118 | return $this
119 | ->useIdoitAPIFactory()
120 | ->getCMDBObject()
121 | ->load((int) $object['id']);
122 | case 1:
123 | $objectID = (int) $objects[0]['id'];
124 |
125 | $this->log->debug('Loading…');
126 |
127 | return $this
128 | ->useIdoitAPIFactory()
129 | ->getCMDBObject()
130 | ->load($objectID);
131 | default:
132 | $this->log
133 | ->debug('Found %s objects', count($objects))
134 | ->printEmptyLine();
135 |
136 | foreach ($objects as $object) {
137 | $this->log->info(
138 | '%s: %s',
139 | $object['id'],
140 | $object['title']
141 | );
142 | }
143 |
144 | $this->log->printEmptyLine();
145 |
146 | if ($this->useUserInteraction()->isInteractive() === false) {
147 | throw new BadMethodCallException(
148 | 'Object selection is ambiguous. Select one by title or numeric identifier'
149 | );
150 | }
151 |
152 | while (true) {
153 | $answer = $this->useUserInteraction()->askQuestion(
154 | 'Select object by title or numeric identifier:'
155 | );
156 |
157 | foreach ($objects as $object) {
158 | if (is_numeric($answer) &&
159 | (int) $answer >= 0 &&
160 | (int) $answer === (int) $object['id']) {
161 | $this->log->debug('Loading…');
162 |
163 | return $this
164 | ->useIdoitAPIFactory()
165 | ->getCMDBObject()
166 | ->load((int) $answer);
167 | } elseif ($answer === $object['title']) {
168 | $this->log->debug('Loading…');
169 |
170 | return $this
171 | ->useIdoitAPIFactory()
172 | ->getCMDBObject()
173 | ->load((int) $object['id']);
174 | }
175 | }
176 |
177 | $this->log->warning('Please try again.');
178 | }
179 | break;
180 | }
181 | }
182 | break;
183 | default:
184 | throw new BadMethodCallException(
185 | 'Too many arguments; please provide only one object title or numeric identifier'
186 | );
187 | }
188 |
189 | return [];
190 | }
191 |
192 | /**
193 | * Print output
194 | *
195 | * @param array $object Everything about the object
196 | *
197 | * @throws Exception on error
198 | */
199 | protected function printOutput(array $object) {
200 | $this->log->printAsOutput()
201 | ->info('Title: %s', $object['title'])
202 | ->info('ID: %s', $object['id'])
203 | ->info('Type: %s', $object['type_title'])
204 | ->info('SYS-ID: %s', $object['sysid'])
205 | ->info('CMDB status: %s', $object['cmdb_status_title'])
206 | ->info('Created: %s', $object['created'])
207 | ->info('Updated: %s', $object['updated']);
208 |
209 | $categoryTypes = ['catg', 'cats', 'custom'];
210 |
211 | $blacklistedCategories = array_merge(
212 | $this
213 | ->useIdoitAPIFactory()
214 | ->getCMDBCategoryInfo()
215 | ->getVirtualCategoryConstants(),
216 | [
217 | 'C__CATG__OVERVIEW',
218 | 'C__CATG__RELATION',
219 | 'C__CATG__LOGBOOK'
220 | ]
221 | );
222 |
223 | foreach ($categoryTypes as $categoryType) {
224 | if (!array_key_exists($categoryType, $object)) {
225 | continue;
226 | }
227 |
228 | foreach ($object[$categoryType] as $category) {
229 | if (in_array($category['const'], $blacklistedCategories)) {
230 | continue;
231 | }
232 |
233 | $this->log
234 | ->printAsMessage()
235 | ->printEmptyLine()
236 | ->info(
237 | '%s [%s]',
238 | $category['title'],
239 | $category['const']
240 | );
241 |
242 | switch (count($category['entries'])) {
243 | case 0:
244 | $this->log->printAsMessage()->info(
245 | 'No entries found'
246 | );
247 | continue 2;
248 | case 1:
249 | $this->log->printAsMessage()->info(
250 | '1 entry found:'
251 | );
252 | break;
253 | default:
254 | $this->log->printAsMessage()->info(
255 | '%s entries found:',
256 | count($category['entries'])
257 | );
258 | break;
259 | }
260 |
261 | try {
262 | $categoryInfo = $this->useCache()->getCategoryInfo($category['const']);
263 | } catch (Exception $e) {
264 | $this->log->printAsMessage()->notice($e->getMessage());
265 | continue;
266 | }
267 |
268 | foreach ($category['entries'] as $entry) {
269 | $this->log->printAsMessage()->printEmptyLine();
270 |
271 | foreach ($entry as $attribute => $value) {
272 | if ($attribute === 'objID') {
273 | continue;
274 | }
275 |
276 | if ($attribute === 'id') {
277 | $this->log
278 | ->printAsOutput()
279 | ->debug('#%s', $value);
280 | continue;
281 | }
282 |
283 | $this->handleAttribute()
284 | ->load($categoryInfo['properties'][$attribute]);
285 |
286 | if ($this->handleAttribute()->ignore()) {
287 | continue;
288 | }
289 |
290 | $value = $this->handleAttribute()
291 | ->encode($value);
292 |
293 | if ($value === '') {
294 | $value = '-';
295 | }
296 |
297 | $this->log->printAsOutput()->info(
298 | '%s: %s',
299 | $categoryInfo['properties'][$attribute]['title'],
300 | $value
301 | );
302 | }
303 | }
304 | }
305 | }
306 | }
307 |
308 | /**
309 | * Print usage of command
310 | *
311 | * @return self Returns itself
312 | */
313 | public function printUsage(): self {
314 | $this->log->info(
315 | <<< EOF
316 | %3\$s
317 |
318 | USAGE
319 | \$ %1\$s %2\$s [OPTIONS] [OBJECT]
320 |
321 | ARGUMENTS
322 | OBJECT Object title or numeric identifier
323 |
324 | COMMON OPTIONS
325 | -c FILE, Include settings stored in a JSON-formatted
326 | --config=FILE configuration file FILE; repeat option for more
327 | than one FILE
328 | -s KEY=VALUE, Add runtime setting KEY with its VALUE; separate
329 | --setting=KEY=VALUE nested keys with ".", for example "key1.key2=123";
330 | repeat option for more than one KEY
331 |
332 | --no-colors Do not print colored messages
333 | -q, --quiet Do not output messages, only errors
334 | -v, --verbose Be more verbose
335 |
336 | -h, --help Print this help or information about a
337 | specific command
338 | --version Print version information
339 |
340 | -y, --yes No user interaction required; answer questions
341 | automatically with default values
342 |
343 | EXAMPLES
344 | # %3\$s by its title:
345 | \$ %1\$s %2\$s mylittleserver
346 |
347 | # …or by its numeric identifier:
348 | \$ %1\$s %2\$s 42
349 |
350 | Handle spaces in title with care:
351 | \$ %1\$s %2\$s "My little server"
352 | \$ %1\$s %2\$s My\\ little\\ server
353 |
354 | # If argument OBJECT is omitted you'll be asked for it:
355 | \$ %1\$s %2\$s
356 | Object? host01.example.com
357 | EOF
358 | ,
359 | $this->config['composer']['extra']['name'],
360 | $this->getName(),
361 | $this->getDescription()
362 | );
363 |
364 | return $this;
365 | }
366 |
367 | }
368 |
--------------------------------------------------------------------------------
/src/Command/Status.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 |
31 | /**
32 | * Command "status"
33 | */
34 | class Status extends Command {
35 |
36 | protected $idoitInfo = [];
37 | protected $apiInfo = [];
38 |
39 | /**
40 | * Execute command
41 | *
42 | * @return self Returns itself
43 | *
44 | * @throws Exception on error
45 | */
46 | public function execute(): self {
47 | $this->log
48 | ->printAsMessage()
49 | ->info($this->getDescription())
50 | ->printEmptyLine();
51 |
52 | return $this
53 | ->getIdoitInfo()
54 | ->getAPIVersion()
55 | ->printStatus();
56 | }
57 |
58 | /**
59 | * @return self Returns itself
60 | *
61 | * @throws Exception on error
62 | */
63 | protected function getIdoitInfo(): self {
64 | $this->idoitInfo = $this->useIdoitAPIFactory()->getCMDB()->readVersion();
65 |
66 | return $this;
67 | }
68 |
69 | /**
70 | * @return self Returns itself
71 | *
72 | * @throws Exception on error
73 | */
74 | protected function getAPIVersion(): self {
75 | try {
76 | $addOns = $this->useIdoitAPIFactory()->getCMDB()->getAddOns();
77 |
78 | foreach ($addOns as $addOn) {
79 | if ($addOn['key'] === 'api') {
80 | $this->apiInfo = $addOn;
81 | break;
82 | }
83 | }
84 | } catch (Exception $e) {
85 | // Suppress any exception…
86 | }
87 |
88 | return $this;
89 | }
90 |
91 | protected function printStatus(): self {
92 | $this->log
93 | ->printAsOutput()
94 | ->info('About i-doit:')
95 | ->info(
96 | 'Version: i-doit %s %s',
97 | strtolower($this->idoitInfo['type']),
98 | $this->idoitInfo['version']
99 | )
100 | ->info('Tenant: %s', $this->idoitInfo['login']['mandator'])
101 | ->info(
102 | 'Link: %s',
103 | str_replace('src/jsonrpc.php', '', $this->config['api']['url'])
104 | )
105 | ->info('API version: %s', $this->apiInfo['version'])
106 | ->info('API entry point: %s', $this->config['api']['url'])
107 | ->printEmptyLine()
108 | ->info('About this script:')
109 | ->info('Name: %s', $this->config['composer']['extra']['name'])
110 | ->info('Description: %s', $this->config['composer']['description'])
111 | ->info('Version: %s', $this->config['composer']['extra']['version'])
112 | ->info('Website: %s', $this->config['composer']['homepage'])
113 | ->info('License: %s', $this->config['composer']['license'])
114 | ->printEmptyLine()
115 | ->info('About you:')
116 | ->info('Name: %s', $this->idoitInfo['login']['name'])
117 | ->info('User name: %s', $this->idoitInfo['login']['username'])
118 | ->info('E-mail: %s', $this->idoitInfo['login']['mail'])
119 | ->info('Language: %s', $this->idoitInfo['login']['language']);
120 |
121 | return $this;
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/src/Command/Types.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Command;
28 |
29 | use \Exception;
30 |
31 | /**
32 | * Command "types"
33 | */
34 | class Types extends Command {
35 |
36 | /**
37 | * Execute command
38 | *
39 | * @return self Returns itself
40 | *
41 | * @throws Exception on error
42 | */
43 | public function execute(): self {
44 | $this->log
45 | ->printAsMessage()
46 | ->info($this->getDescription())
47 | ->printEmptyLine();
48 |
49 | $types = $this->useCache()->getObjectTypes();
50 |
51 | $types = array_filter($types, [$this, 'filterObjectTypes']);
52 |
53 | usort($types, [$this, 'sort']);
54 |
55 | $groups = $this->group($types);
56 |
57 | $this->formatGroups($groups);
58 |
59 | return $this;
60 | }
61 |
62 | /**
63 | * Accept only object types with status "normal" [2]
64 | *
65 | * @param array $type Object type information
66 | *
67 | * @return bool
68 | */
69 | protected function filterObjectTypes(array $type): bool {
70 | return $type['status'] === '2';
71 | }
72 |
73 | /**
74 | * Group object types by localized group names
75 | *
76 | * @param array $types Object type information
77 | *
78 | * @return array
79 | */
80 | protected function group(array $types): array {
81 | $groups = [];
82 |
83 | foreach ($types as $type) {
84 | if (!array_key_exists($type['type_group_title'], $groups)) {
85 | $groups[$type['type_group_title']] = [];
86 | }
87 |
88 | $groups[$type['type_group_title']][] = $type;
89 | }
90 |
91 | return $groups;
92 | }
93 |
94 | /**
95 | * Print object type groups
96 | *
97 | * @param array $groups Object type groups
98 | */
99 | protected function formatGroups(array $groups) {
100 | switch (count($groups)) {
101 | case 0:
102 | $this->log
103 | ->printAsMessage()
104 | ->debug('No groups found');
105 | break;
106 | case 1:
107 | $this->log
108 | ->printAsMessage()
109 | ->debug('1 group type found');
110 | break;
111 | default:
112 | $this->log
113 | ->printAsMessage()
114 | ->debug('%s groups found', count($groups));
115 | break;
116 | }
117 |
118 | foreach ($groups as $group => $types) {
119 | $this->log
120 | ->info('%s:', $group);
121 |
122 | $this->formatList($types);
123 |
124 | $this->log
125 | ->printAsMessage()
126 | ->printEmptyLine();
127 | }
128 | }
129 |
130 | /**
131 | * Print object types
132 | *
133 | * @param array $types List of object types
134 | */
135 | protected function formatList(array $types) {
136 | switch (count($types)) {
137 | case 0:
138 | $this->log
139 | ->printAsMessage()
140 | ->debug('No object types found');
141 | break;
142 | case 1:
143 | $this->log
144 | ->printAsMessage()
145 | ->debug('1 object type found');
146 | break;
147 | default:
148 | $this->log
149 | ->printAsMessage()
150 | ->debug('%s object types found', count($types));
151 | break;
152 | }
153 |
154 | $this->log->printEmptyLine();
155 |
156 | foreach ($types as $type) {
157 | $this->log
158 | ->printAsOutput()
159 | ->info($this->format($type));
160 | }
161 | }
162 |
163 | /**
164 | * Format object type
165 | *
166 | * @param array $type Object type information
167 | *
168 | * @return string
169 | */
170 | protected function format(array $type): string {
171 | return sprintf(
172 | '%s [%s]',
173 | $type['title'],
174 | $type['const']
175 | );
176 | }
177 |
178 | /**
179 | * Sort object types in alphabetical order by their localized titles
180 | *
181 | * @param array $a Object type information
182 | * @param array $b Object type information
183 | *
184 | * @return int
185 | */
186 | protected function sort(array $a, array $b): int {
187 | return strcmp($a['title'], $b['title']);
188 | }
189 |
190 | }
191 |
--------------------------------------------------------------------------------
/src/Service/Cache.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Service;
28 |
29 | use \DirectoryIterator;
30 | use \Exception;
31 | use \RuntimeException;
32 |
33 | /**
34 | * Cache files
35 | */
36 | class Cache extends Service {
37 |
38 | /**
39 | * Cached object types
40 | *
41 | * @var array Indexed array of associative arrays
42 | */
43 | protected $cacheObjectTypes = [];
44 |
45 | /**
46 | * Cached categories
47 | *
48 | * @var array Indexed array of associative arrays
49 | */
50 | protected $cacheCategories = [];
51 |
52 | /**
53 | * Cached assignments between categories and object types
54 | *
55 | * @var array Indexed array of associative arrays
56 | */
57 | protected $cacheAssignedCategories = [];
58 |
59 | /**
60 | * Cache directory for current i-doit host
61 | *
62 | * @var string
63 | */
64 | protected $hostDir;
65 |
66 | /**
67 | * Is cache for current host available?
68 | *
69 | * @return bool
70 | *
71 | * @throws Exception on error
72 | */
73 | public function isCached(): bool {
74 | $hostDir = $this->getHostDir();
75 |
76 | if (!is_dir($hostDir)) {
77 | return false;
78 | }
79 |
80 | $dir = new DirectoryIterator($hostDir);
81 |
82 | foreach ($dir as $file) {
83 | if ($file->isDot() || $file->isDir()) {
84 | continue;
85 | }
86 |
87 | if ($file->isFile() === false) {
88 | continue;
89 | }
90 |
91 | if ($this->config['cacheLifetime'] > 0 &&
92 | (time() - $this->config['cacheLifetime'] > $file->getCTime())) {
93 | return false;
94 | }
95 |
96 | // First file is valid – this is all we need to know:
97 | return true;
98 | }
99 |
100 | return false;
101 | }
102 |
103 | /**
104 | * Reads list of object types from cache
105 | *
106 | * @return array
107 | *
108 | * @throws Exception on error
109 | */
110 | public function getObjectTypes(): array {
111 | $hostDir = $this->getHostDir();
112 | $filePath = $hostDir . '/object_types';
113 | return $this->unserializeFromFile($filePath);
114 | }
115 |
116 | /**
117 | * Converts an object type title into a object type constant
118 | *
119 | * @param string $title Object type title
120 | *
121 | * @return string Object type constant, otherwise \Exception is thrown
122 | *
123 | * @throws Exception on error
124 | */
125 | public function getObjectTypeConstantByTitle(string $title) {
126 | $objectTypes = $this->getObjectTypes();
127 |
128 | foreach ($objectTypes as $objectType) {
129 | if (strtolower($objectType['title']) === strtolower($title)) {
130 | return $objectType['const'];
131 | }
132 | }
133 |
134 | throw new RuntimeException(sprintf(
135 | 'Unable to find constant for object type "%s"',
136 | $title
137 | ));
138 | }
139 |
140 | /**
141 | * Reads information about a category from cache
142 | *
143 | * @param string $categoryConst Category constant
144 | *
145 | * @return array
146 | *
147 | * @throws Exception on error
148 | */
149 | public function getCategoryInfo(string $categoryConst): array {
150 | $hostDir = $this->getHostDir();
151 | $filePath = $hostDir . '/category__' . $categoryConst;
152 | return $this->unserializeFromFile($filePath);
153 | }
154 |
155 | /**
156 | * Reads list of categories from cache which are assigned to an object type
157 | *
158 | * @param string $type Object type constant
159 | *
160 | * @return array ['catg' => [['id' => 1, …], ['id' => 1, …]], 'cats' => …]
161 | *
162 | * @throws Exception on error
163 | */
164 | public function getAssignedCategories(string $type): array {
165 | $hostDir = $this->getHostDir();
166 | $filePath = $hostDir . '/object_type__' . $type;
167 | return $this->unserializeFromFile($filePath);
168 | }
169 |
170 | /**
171 | * Reads a list of categories from cache
172 | *
173 | * @return array
174 | *
175 | * @throws Exception on error
176 | */
177 | public function getCategories(): array {
178 | $categories = [];
179 | $hostDir = $this->getHostDir();
180 |
181 | $dir = new DirectoryIterator($hostDir);
182 |
183 | foreach ($dir as $file) {
184 | if ($file->isFile() === false) {
185 | continue;
186 | }
187 |
188 | if (strpos($file->getFilename(), 'category__') !== 0) {
189 | continue;
190 | }
191 |
192 | $filePath = $hostDir . '/' . $file->getFilename();
193 |
194 | $categories[] = $this->unserializeFromFile($filePath);
195 | }
196 |
197 | return $categories;
198 | }
199 |
200 | /**
201 | * Gets cache directory for current i-doit host
202 | *
203 | * @return string
204 | *
205 | * @throws Exception when configuration settings are missing
206 | */
207 | public function getHostDir(): string {
208 | if (!array_key_exists('api', $this->config) ||
209 | !array_key_exists('url', $this->config['api'])) {
210 | throw new Exception(sprintf(
211 | 'No proper configuration found' . PHP_EOL .
212 | 'Run "%s init" to create configuration settings',
213 | $this->config['composer']['extra']['name']
214 | ));
215 | }
216 |
217 | if (!isset($this->hostDir)) {
218 | $this->hostDir = $this->config['dataDir'] . '/' .
219 | sha1($this->config['api']['url']);
220 | }
221 |
222 | return $this->hostDir;
223 | }
224 |
225 | /**
226 | * Read file and create PHP code out of it
227 | *
228 | * @param string $filePath Path to file
229 | *
230 | * @return mixed
231 | *
232 | * @throws RuntimeException on error
233 | */
234 | protected function unserializeFromFile(string $filePath) {
235 | if (!is_readable($filePath)) {
236 | throw new RuntimeException(sprintf(
237 | 'File "%s" not found or not accessible',
238 | $filePath
239 | ));
240 | }
241 |
242 | $fileContent = file_get_contents($filePath);
243 |
244 | if (!is_string($fileContent)) {
245 | throw new RuntimeException(sprintf(
246 | 'Unable to read file "%s"',
247 | $filePath
248 | ));
249 | }
250 |
251 | return unserialize($fileContent);
252 | }
253 |
254 | }
255 |
--------------------------------------------------------------------------------
/src/Service/IdoitAPI.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Service;
28 |
29 | use \Exception;
30 | use \BadMethodCallException;
31 | use \RuntimeException;
32 |
33 | /**
34 | * i-doit API
35 | */
36 | class IdoitAPI extends Service {
37 |
38 | /**
39 | * @var IdoitAPIFactory
40 | */
41 | protected $idoitAPIFactory;
42 |
43 | /**
44 | * Setup service
45 | *
46 | * @param IdoitAPIFactory $idoitAPIFactory
47 | *
48 | * @return self Returns itself
49 | */
50 | public function setUp(IdoitAPIFactory $idoitAPIFactory): self{
51 | $this->idoitAPIFactory = $idoitAPIFactory;
52 |
53 | return $this;
54 | }
55 |
56 | /**
57 | * Fetches objects from i-doit
58 | *
59 | * @param array $filter Associative array, see CMDBObjects::read()
60 | * @param array $categories List of category constants for more information
61 | * @param int $objectCount How many objects will be fetched? Defaults to 0 (ignore it)
62 | *
63 | * @return array Keys: object identifiers; values: object attributes
64 | *
65 | * @throws Exception on error
66 | */
67 | public function fetchObjects(array $filter, array $categories = [], int $objectCount = 0): array {
68 | $limit = $this->config['limitBatchRequests'];
69 |
70 | $objects = [];
71 | $offset = 0;
72 |
73 | if (array_key_exists('title', $filter) && strpos($filter['title'], '*') !== false) {
74 | $readFilter = array_filter($filter, function ($key) {
75 | return $key !== 'title';
76 | }, ARRAY_FILTER_USE_KEY);
77 | } else {
78 | $readFilter = $filter;
79 | }
80 |
81 | while (true) {
82 | if ($limit > 0) {
83 | $result = $this->idoitAPIFactory->getCMDBObjects()->read($readFilter, $limit, (int) $offset);
84 |
85 | if ($objectCount > 0 && $objectCount >= $limit) {
86 | if ($offset === 0) {
87 | $this->log->debug(
88 | 'Fetch first %s objects from %s to %s',
89 | $limit,
90 | $offset + 1,
91 | $offset + $limit
92 | );
93 | } elseif (($objectCount - $offset) === 1) {
94 | $this->log->debug(
95 | 'Fetch last object'
96 | );
97 | } elseif (($objectCount - $offset) > 1 && ($offset + $limit) > $objectCount) {
98 | $this->log->debug(
99 | 'Fetch last %s objects',
100 | $objectCount - $offset
101 | );
102 | } elseif (($objectCount - $offset) > 1) {
103 | $this->log->debug(
104 | 'Fetch next %s objects from %s to %s',
105 | $limit,
106 | $offset + 1,
107 | $offset + $limit
108 | );
109 | }
110 | }
111 |
112 | $offset += $limit;
113 | } else {
114 | $result = $this->idoitAPIFactory->getCMDBObjects()->read($readFilter);
115 | }
116 |
117 | if (count($result) === 0) {
118 | break;
119 | }
120 |
121 | if (array_key_exists('title', $filter) && strpos($filter['title'], '*') !== false) {
122 | $result = array_filter($result, function ($object) use ($filter) {
123 | return fnmatch($filter['title'], $object['title']);
124 | });
125 | }
126 |
127 | $objectIDs = [];
128 |
129 | foreach ($result as $object) {
130 | $objectID = (int) $object['id'];
131 |
132 | $objectIDs[] = $objectID;
133 |
134 | $objects[$objectID] = [
135 | 'id' => $objectID,
136 | 'title' => $object['title'],
137 | 'type' => (int) $object['type'],
138 | 'type_title' => $object['type_title'],
139 | ];
140 | }
141 |
142 | if (count($categories) > 0) {
143 | foreach ($categories as $categoryConstant) {
144 | $categoryResults = $this
145 | ->idoitAPIFactory
146 | ->getCMDBCategory()
147 | ->batchRead($objectIDs, [$categoryConstant]);
148 |
149 | $count = 0;
150 | foreach ($categoryResults as $categoryResult) {
151 | $objects[$objectIDs[$count]][$categoryConstant] = $categoryResult;
152 |
153 | $count++;
154 | }
155 | }
156 | }
157 |
158 | if (count($result) < $limit) {
159 | break;
160 | }
161 |
162 | if ($limit <= 0) {
163 | break;
164 | }
165 | }
166 |
167 | return $objects;
168 | }
169 |
170 | /**
171 | * @param string $candidate
172 | *
173 | * @return array Object information, for example:
174 | * - id (numeric indentifier as string)
175 | * - title (string)
176 | *
177 | * @throws Exception when object not found
178 | */
179 | public function identifyObject(string $candidate): array {
180 | if (is_numeric($candidate) && (int) $candidate > 0) {
181 | $object = $this->idoitAPIFactory->getCMDBObject()->read((int) $candidate);
182 |
183 | if (count($object) > 0) {
184 | return $object;
185 | }
186 |
187 | throw new BadMethodCallException(sprintf(
188 | 'Object not found by numeric identifier %s',
189 | $candidate
190 | ));
191 | }
192 |
193 | $objects = $this->fetchObjects([
194 | 'title' => $candidate
195 | ]);
196 |
197 | switch (count($objects)) {
198 | case 0:
199 | throw new BadMethodCallException(sprintf(
200 | 'Object not found by title "%s"',
201 | $candidate
202 | ));
203 | case 1:
204 | $object = end($objects);
205 | return $object;
206 | default:
207 | throw new RuntimeException(sprintf(
208 | 'Object title "%s" is ambiguous',
209 | $candidate
210 | ));
211 | }
212 | }
213 |
214 | /**
215 | * Translate object titles into identifiers
216 | *
217 | * Keep in mind that there could be objects which
218 | *
219 | * - have none of provided titles or
220 | * - have the same titles.
221 | *
222 | * As a result the returned array could have a smaller, bigger or even size as the provided titles array.
223 | *
224 | * @param array $titles Object titles as strings
225 | *
226 | * @return array List of object identifiers
227 | *
228 | * @throws Exception on error
229 | */
230 | public function fetchObjectIDsByTitles(array $titles): array {
231 | $objectdIDs = [];
232 |
233 | switch (count($titles)) {
234 | case 0:
235 | throw new BadMethodCallException('Empty list of object titles');
236 | case 1:
237 | $title = end($titles);
238 |
239 | $objects = $this->idoitAPIFactory->getCMDBObjects()->read([
240 | 'title' => $title
241 | ]);
242 |
243 | foreach ($objects as $object) {
244 | $objectdIDs[] = (int) $object['id'];
245 | }
246 | break;
247 | default:
248 | $requests = [];
249 |
250 | foreach ($titles as $title) {
251 | $requests[] = [
252 | 'method' => 'cmdb.objects.read',
253 | 'params' => [
254 | 'filter' => [
255 | 'title' => $title
256 | ]
257 | ]
258 | ];
259 | }
260 |
261 | $result = $this->idoitAPIFactory->getAPI()->batchRequest($requests);
262 |
263 | foreach ($result as $objects) {
264 | foreach ($objects as $object) {
265 | $objectdIDs[] = (int) $object['id'];
266 | }
267 | }
268 | break;
269 | }
270 |
271 | return $objectdIDs;
272 | }
273 |
274 | }
275 |
--------------------------------------------------------------------------------
/src/Service/IdoitAPIFactory.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Service;
28 |
29 | use \Exception;
30 | use \RuntimeException;
31 | use bheisig\cli\Log;
32 | use bheisig\idoitapi\API;
33 | use bheisig\idoitapi\Request;
34 | use bheisig\idoitapi\CMDBCategory;
35 | use bheisig\idoitapi\CMDBCategoryInfo;
36 | use bheisig\idoitapi\CMDBDialog;
37 | use \bheisig\idoitapi\CMDBLogbook;
38 | use bheisig\idoitapi\CMDBObject;
39 | use bheisig\idoitapi\CMDBObjects;
40 | use bheisig\idoitapi\CMDBObjectTypeCategories;
41 | use bheisig\idoitapi\CMDBObjectTypes;
42 | use bheisig\idoitapi\Idoit;
43 | use bheisig\idoitapi\Subnet;
44 |
45 | /**
46 | * i-doit API factory
47 | */
48 | class IdoitAPIFactory extends Service {
49 |
50 | /**
51 | * API
52 | *
53 | * @var API
54 | */
55 | protected $api;
56 |
57 | /**
58 | * Factory
59 | *
60 | * @var array
61 | */
62 | protected $instances = [];
63 |
64 | /**
65 | * Constructor
66 | *
67 | * @param array $config Configuration settings
68 | * @param Log $log Logger
69 | *
70 | * @throws Exception when configuration settings are missing
71 | */
72 | public function __construct(array $config, Log $log) {
73 | parent::__construct($config, $log);
74 |
75 | try {
76 | $this->api = new API($this->config['api']);
77 | if (array_key_exists(API::USERNAME, $this->config['api']) &&
78 | array_key_exists(API::PASSWORD, $this->config['api'])) {
79 | $this->api->login();
80 | }
81 | } catch (Exception $e) {
82 | throw new Exception(
83 | 'No proper configuration for i-doit API calls: ' . $e->getMessage()
84 | );
85 | }
86 | }
87 |
88 | /**
89 | * Get API instance
90 | *
91 | * @return API
92 | */
93 | public function getAPI(): API {
94 | return $this->api;
95 | }
96 |
97 | /**
98 | * Factory
99 | *
100 | * @param string $class Class name
101 | *
102 | * @return mixed
103 | */
104 | protected function getInstanceOf($class) {
105 | if (!array_key_exists($class, $this->instances)) {
106 | if (is_a($class, Request::class, true) === false) {
107 | throw new RuntimeException(sprintf(
108 | '%s is not an API request class',
109 | $class
110 | ));
111 | }
112 |
113 | $this->instances[$class] = new $class($this->api);
114 | }
115 |
116 | return $this->instances[$class];
117 | }
118 |
119 | /**
120 | * Factory for CMDB
121 | *
122 | * @return Idoit
123 | */
124 | public function getCMDB(): Idoit {
125 | return $this->getInstanceOf(Idoit::class);
126 | }
127 |
128 | /**
129 | * Factory for CMDBObject
130 | *
131 | * @return CMDBObject
132 | */
133 | public function getCMDBObject(): CMDBObject {
134 | return $this->getInstanceOf(CMDBObject::class);
135 | }
136 |
137 | /**
138 | * Factory for CMDBObjects
139 | *
140 | * @return CMDBObjects
141 | */
142 | public function getCMDBObjects(): CMDBObjects {
143 | return $this->getInstanceOf(CMDBObjects::class);
144 | }
145 |
146 | /**
147 | * Factory for CMDBObjectTypeCategories
148 | *
149 | * @return CMDBObjectTypeCategories
150 | */
151 | public function getCMDBObjectTypeCategories(): CMDBObjectTypeCategories {
152 | return $this->getInstanceOf(CMDBObjectTypeCategories::class);
153 | }
154 |
155 | /**
156 | * Factory for CMDBObjectTypes
157 | *
158 | * @return CMDBObjectTypes
159 | */
160 | public function getCMDBObjectTypes(): CMDBObjectTypes {
161 | return $this->getInstanceOf(CMDBObjectTypes::class);
162 | }
163 |
164 | /**
165 | * Factory for CMDBCategory
166 | *
167 | * @return CMDBCategory
168 | */
169 | public function getCMDBCategory(): CMDBCategory {
170 | return $this->getInstanceOf(CMDBCategory::class);
171 | }
172 |
173 | /**
174 | * Factory for CMDBCategoryInfo
175 | *
176 | * @return CMDBCategoryInfo
177 | */
178 | public function getCMDBCategoryInfo(): CMDBCategoryInfo {
179 | return $this->getInstanceOf(CMDBCategoryInfo::class);
180 | }
181 |
182 | /**
183 | * Factory for CMDBDialog
184 | *
185 | * @return CMDBDialog
186 | */
187 | public function getCMDBDialog(): CMDBDialog {
188 | return $this->getInstanceOf(CMDBDialog::class);
189 | }
190 |
191 | /**
192 | * Factory for CMDBDialog
193 | *
194 | * @return CMDBLogbook
195 | */
196 | public function getCMDBLogbook(): CMDBLogbook {
197 | return $this->getInstanceOf(CMDBLogbook::class);
198 | }
199 |
200 | /**
201 | * Factory for Subnet
202 | *
203 | * @return Subnet
204 | */
205 | public function getSubnet(): Subnet {
206 | return $this->getInstanceOf(Subnet::class);
207 | }
208 |
209 | }
210 |
--------------------------------------------------------------------------------
/src/Service/IdoitStatus.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Service;
28 |
29 | /**
30 | * i-doit status
31 | */
32 | class IdoitStatus extends Service {
33 |
34 | const STATUS_NORMAL = 2;
35 | const STATUS_ARCHIVED = 3;
36 | const STATUS_DELETED = 4;
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/Service/PrintData.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Service;
28 |
29 | use \Exception;
30 | use \BadMethodCallException;
31 | use \RuntimeException;
32 | use bheisig\cli\Log;
33 |
34 | /**
35 | * Print data
36 | */
37 | class PrintData extends Service {
38 |
39 | /**
40 | * Offset
41 | *
42 | * @var int Defaults to 0
43 | */
44 | protected $offset = 0;
45 |
46 | /**
47 | * i-doit API
48 | *
49 | * @var IdoitAPI
50 | */
51 | protected $idoitAPI;
52 |
53 | /**
54 | * i-doit API factory
55 | *
56 | * @var IdoitAPIFactory
57 | */
58 | protected $idoitAPIFactory;
59 |
60 | /**
61 | * Set offset
62 | *
63 | * @param int $offset Offset
64 | *
65 | * @return self Returns itself
66 | *
67 | * @throws BadMethodCallException on invalid parameter
68 | */
69 | public function setOffset(int $offset): self {
70 | if ($offset < 0) {
71 | throw new BadMethodCallException('Offset must be greater or equal zero');
72 | }
73 |
74 | $this->offset = $offset;
75 |
76 | return $this;
77 | }
78 |
79 | /**
80 | * Print category entry
81 | *
82 | * @param array $entry Entry with key-value pairs of attribute/value
83 | * @param array $attributeDefinitions Attribute descriptions provided by API method "cmdb.category.info"
84 | * @param HandleAttribute $handleAttribute Attribute service
85 | * @param int $level Log level; defaults to "info"
86 | * @param string $printAs Print log as output (default) or as message
87 | *
88 | * @return self Returns itself
89 | *
90 | * @throws Exception on error
91 | */
92 | public function printEntry(
93 | array $entry,
94 | array $attributeDefinitions,
95 | HandleAttribute $handleAttribute,
96 | int $level = Log::INFO,
97 | string $printAs = Log::PRINT_AS_OUTPUT
98 | ): self {
99 | if (count($entry) === 0) {
100 | throw new BadMethodCallException('Entry is empty');
101 | }
102 |
103 | if (count($attributeDefinitions) === 0) {
104 | throw new BadMethodCallException('Empty attribute definitions');
105 | }
106 |
107 | foreach ($entry as $attribute => $value) {
108 | if ($attribute === 'id' || $attribute === 'objID') {
109 | continue;
110 | }
111 |
112 | if (!array_key_exists($attribute, $attributeDefinitions)) {
113 | continue;
114 | }
115 |
116 | $handleAttribute->load($attributeDefinitions[$attribute]);
117 |
118 | if ($handleAttribute->ignore() || $handleAttribute->isReadonly()) {
119 | continue;
120 | }
121 |
122 | $encodedValue = $handleAttribute->encode($value);
123 |
124 | if (strlen($encodedValue) === 0) {
125 | $encodedValue = '–';
126 | }
127 |
128 | if (!array_key_exists('title', $attributeDefinitions[$attribute])) {
129 | throw new RuntimeException(sprintf(
130 | 'No localized title found for attribute "%s"',
131 | $attribute
132 | ));
133 | }
134 |
135 | $attributeTitle = $attributeDefinitions[$attribute]['title'];
136 |
137 | switch ($printAs) {
138 | case Log::PRINT_AS_OUTPUT:
139 | $this->log->printAsOutput();
140 | break;
141 | case Log::PRINT_AS_MESSAGE:
142 | $this->log->printAsMessage();
143 | break;
144 | }
145 |
146 | $this->log->event(
147 | $level,
148 | '%s%s: %s',
149 | str_repeat(' ', $this->offset),
150 | $attributeTitle,
151 | $encodedValue
152 | );
153 | }
154 |
155 | return $this;
156 | }
157 |
158 | public function printDialogEntries(
159 | array $entries,
160 | int $level = Log::INFO,
161 | string $printAs = Log::PRINT_AS_OUTPUT
162 | ): self {
163 | switch ($printAs) {
164 | case Log::PRINT_AS_OUTPUT:
165 | $this->log->printAsOutput();
166 | break;
167 | case Log::PRINT_AS_MESSAGE:
168 | $this->log->printAsMessage();
169 | break;
170 | }
171 |
172 | foreach ($entries as $entry) {
173 | $this->log->event(
174 | $level,
175 | '%s%s [%s]',
176 | str_repeat(' ', $this->offset),
177 | $entry['title'],
178 | $entry['id']
179 | );
180 | }
181 |
182 | return $this;
183 | }
184 |
185 | }
186 |
--------------------------------------------------------------------------------
/src/Service/Service.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Service;
28 |
29 | use bheisig\cli\Log;
30 |
31 | /**
32 | * Base class for services
33 | */
34 | abstract class Service {
35 |
36 | /**
37 | * Configuration settings as key-value store
38 | *
39 | * @var array Associative array
40 | */
41 | protected $config = [];
42 |
43 | /**
44 | * Logger
45 | *
46 | * @var Log
47 | */
48 | protected $log;
49 |
50 | /**
51 | * Constructor
52 | *
53 | * @param array $config Configuration settings
54 | * @param Log $log Logger
55 | */
56 | public function __construct(array $config, Log $log) {
57 | $this->config = $config;
58 | $this->log = $log;
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/Service/Validate.php:
--------------------------------------------------------------------------------
1 | .
18 | *
19 | * @author Benjamin Heisig
20 | * @copyright Copyright (C) 2016-17 Benjamin Heisig
21 | * @license http://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License (AGPL)
22 | * @link https://github.com/bheisig/i-doit-cli
23 | */
24 |
25 | declare(strict_types=1);
26 |
27 | namespace bheisig\idoitcli\Service;
28 |
29 | /**
30 | * Validate data
31 | */
32 | class Validate extends Service {
33 |
34 | public function isOneLiner(string $value): bool {
35 | $length = strlen($value);
36 |
37 | if ($length <= 0) {
38 | return false;
39 | }
40 |
41 | if ($length > HandleAttribute::SHORT_TEXT_LENGTH) {
42 | return false;
43 | }
44 |
45 | return true;
46 | }
47 |
48 | public function isIDAsString(string $id) {
49 | return is_numeric($id) && (int) $id > 0;
50 | }
51 |
52 | public function isID(int $id): bool {
53 | return $id > 0;
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------