├── .gitignore ├── .rubocop.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── README_BMCS.md ├── Rakefile ├── SECURITY.md ├── docs └── rename.md ├── knife-oci.gemspec ├── lib ├── chef │ └── knife │ │ ├── oci_ad_list.rb │ │ ├── oci_common_options.rb │ │ ├── oci_compartment_list.rb │ │ ├── oci_helper.rb │ │ ├── oci_helper_show.rb │ │ ├── oci_image_list.rb │ │ ├── oci_server_create.rb │ │ ├── oci_server_delete.rb │ │ ├── oci_server_list.rb │ │ ├── oci_server_show.rb │ │ ├── oci_shape_list.rb │ │ ├── oci_subnet_list.rb │ │ └── oci_vcn_list.rb └── knife-oci │ └── version.rb ├── spec ├── integration │ ├── list_commands_integ_spec.rb │ └── server_create_integ_spec.rb ├── resources │ ├── config_for_unit_tests │ ├── dummy_ssh_key │ ├── dummy_ssh_key.pub │ ├── example_user_data.txt │ └── knife.rb ├── spec_helper.rb └── unit │ ├── ad_list_spec.rb │ ├── bmcs_common_spec.rb │ ├── compartment_list_spec.rb │ ├── help_spec.rb │ ├── image_list_spec.rb │ ├── server_create_spec.rb │ ├── server_delete_spec.rb │ ├── server_list_spec.rb │ ├── server_show_spec.rb │ ├── shape_list_spec.rb │ ├── subnet_list_spec.rb │ └── vcn_list_spec.rb ├── test_output └── help │ ├── ad_list.txt │ ├── compartment_list.txt │ ├── image_list.txt │ ├── oci.txt │ ├── server_create.txt │ ├── server_delete.txt │ ├── server_list.txt │ ├── shape_list.txt │ ├── subnet_list.txt │ └── vcn_list.txt └── wercker.yml /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .chef/ 3 | .idea/ 4 | .DS_Store 5 | chef-repo/ 6 | coverage/ 7 | dist/ 8 | internal_resources/ 9 | Gemfile.lock 10 | test_output/list_commands 11 | test_output/server_create 12 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - '.chef/**/*' 4 | - 'chef-repo/**/*' 5 | 6 | Metrics/LineLength: 7 | Max: 200 8 | 9 | Metrics/ClassLength: 10 | Max: 500 11 | 12 | Metrics/MethodLength: 13 | Max: 500 14 | 15 | Metrics/AbcSize: 16 | Max: 100 17 | 18 | Metrics/PerceivedComplexity: 19 | Max: 9 20 | 21 | Metrics/BlockLength: 22 | Max: 200 23 | 24 | Metrics/CyclomaticComplexity: 25 | Max: 8 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/). 5 | 6 | ## 2.0.2 - 2019-01-17 7 | ### Fixed 8 | - Fixed SSH failure during "knife oci server create" when using newer Chef Workstation and ChefDK versions. 9 | 10 | ## 2.0.1 - 2019-01-09 11 | ### Changed 12 | - Change default ssh timeout for `knife oci server create` from 180s to 300s. 13 | 14 | ### Fixed 15 | - Fix help text in `knife oci server show --instance_id` and `knife oci server delete --purge` commands. 16 | 17 | ### Added 18 | - Support for multiple VNICs. `knife oci server show` can show multiple VNICs. 19 | - Support in `--purge` param for `knife oci server delete` to remove the Chef Client as well as the Chef Node. 20 | 21 | ## 2.0.0 - 2017-09-11 22 | ### Changed 23 | - Changed the gem name from knife-bmcs to knife-oci, and changed all occurances of BMCS to OCI. Details can be found [here](docs/rename.md). 24 | 25 | ### Fixed 26 | - List commands will now make multiple service calls to ensure that all results are retrieved, up to the given limit. 27 | 28 | ### Added 29 | - Support for running the knife-oci plugin on Windows. 30 | - '--purge' param for 'knife oci server delete' to optionally remove the node from the Chef Server. 31 | - Additional info in 'knife oci server show' 32 | 33 | ## 1.2.0 - 2017-09-11 34 | ### Deprecated 35 | - The knife-bmcs gem has been deprecated. Users should switch to knife-oci, which provides a similar set of commands under 'knife oci'. Details can be found [here](docs/rename.md). 36 | 37 | ## 1.1.0 - 2017-08-16 38 | ### Added 39 | - List compartments command, 'knife bmcs compartment list' 40 | - List VCNs command, 'knife bmcs vcn list' 41 | - List subnets command, 'knife bmcs subnet list --vcn-id ' 42 | - Server show command, 'knife bmcs server show --instance-id ' 43 | - Server delete command, 'knife bmcs server delete --instance-id ' 44 | - Region parameter for all BMCS commands, '--region' 45 | - Wait time parameters for 'knife bmcs server create' 46 | 47 | ## 1.0.0 - 2017-06-09 48 | ### Added 49 | - Initial Release 50 | - Support for 'knife bmcs server create' and several list commands. 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this repository 2 | 3 | We welcome your contributions! There are multiple ways to contribute. 4 | 5 | ## Opening issues 6 | 7 | For bugs or enhancement requests, please file a GitHub issue unless it's 8 | security related. When filing a bug remember that the better written the bug is, 9 | the more likely it is to be fixed. If you think you've found a security 10 | vulnerability, do not raise a GitHub issue and follow the instructions in our 11 | [security policy](./SECURITY.md). 12 | 13 | ## Contributing code 14 | 15 | We welcome your code contributions. Before submitting code via a pull request, 16 | you will need to have signed the [Oracle Contributor Agreement][OCA] (OCA) and 17 | your commits need to include the following line using the name and e-mail 18 | address you used to sign the OCA: 19 | 20 | ```text 21 | Signed-off-by: Your Name 22 | ``` 23 | 24 | This can be automatically added to pull requests by committing with `--sign-off` 25 | or `-s`, e.g. 26 | 27 | ```text 28 | git commit --signoff 29 | ``` 30 | 31 | Only pull requests from committers that can be verified as having signed the OCA 32 | can be accepted. 33 | 34 | ## Pull request process 35 | 36 | 1. Ensure there is an issue created to track and discuss the fix or enhancement 37 | you intend to submit. 38 | 1. Fork this repository. 39 | 1. Create a branch in your fork to implement the changes. We recommend using 40 | the issue number as part of your branch name, e.g. `1234-fixes`. 41 | 1. Ensure that any documentation is updated with the changes that are required 42 | by your change. 43 | 1. Ensure that any samples are updated if the base image has been changed. 44 | 1. Submit the pull request. *Do not leave the pull request blank*. Explain exactly 45 | what your changes are meant to do and provide simple steps on how to validate. 46 | your changes. Ensure that you reference the issue you created as well. 47 | 1. We will assign the pull request to 2-3 people for review before it is merged. 48 | 49 | ## Code of conduct 50 | 51 | Follow the [Golden Rule](https://en.wikipedia.org/wiki/Golden_Rule). If you'd 52 | like more specific guidelines, see the [Contributor Covenant Code of Conduct][COC]. 53 | 54 | [OCA]: https://oca.opensource.oracle.com 55 | [COC]: https://www.contributor-covenant.org/version/1/4/code-of-conduct/ 56 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | source 'https://rubygems.org' 4 | gemspec 5 | 6 | group :development do 7 | gem 'chef' 8 | gem 'rake' 9 | gem 'rspec' 10 | gem 'rubocop', '~> 0.48.1' 11 | gem 'simplecov' 12 | end 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, 2023 Oracle and/or its affiliates.  All rights reserved. 2 | 3 | This software is dual-licensed to you under the Universal Permissive License (UPL) and Apache License 2.0.  See below for license terms.  You may choose either license, or both. 4 |  ____________________________ 5 | The Universal Permissive License (UPL), Version 1.0 6 | 7 | Subject to the condition set forth below, permission is hereby granted to any 8 | person obtaining a copy of this software, associated documentation and/or data 9 | (collectively the "Software"), free of charge and under any and all copyright 10 | rights in the Software, and any and all patent rights owned or freely 11 | licensable by each licensor hereunder covering either (i) the unmodified 12 | Software as contributed to or provided by such licensor, or (ii) the Larger 13 | Works (as defined below), to deal in both 14 | 15 | (a) the Software, and 16 | (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if 17 | one is included with the Software (each a "Larger Work" to which the Software 18 | is contributed by such licensors), 19 | 20 | without restriction, including without limitation the rights to copy, create 21 | derivative works of, display, perform, and distribute the Software and make, 22 | use, sell, offer for sale, import, export, have made, and have sold the 23 | Software and the Larger Work(s), and to sublicense the foregoing rights on 24 | either these or other terms. 25 | 26 | This license is subject to the following condition: 27 | The above copyright notice and either this complete permission notice or at 28 | a minimum a reference to the UPL must be included in all copies or 29 | substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | 40 | The Apache Software License, Version 2.0 41 | Copyright (c) 2017, Oracle and/or its affiliates.  All rights reserved. 42 | 43 | Licensed under the Apache License, Version 2.0 (the "License"); You may not use this product except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. A copy of the license is also reproduced below. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 44 | 45 | Apache License 46 | 47 | Version 2.0, January 2004 48 | 49 | http://www.apache.org/licenses/ 50 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 51 | 1. Definitions. 52 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 53 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 54 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 55 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 56 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 57 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 58 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 59 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 60 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 61 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 62 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 63 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 64 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 65 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 66 | You must cause any modified files to carry prominent notices stating that You changed the files; and 67 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 68 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 69 | 70 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 71 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 72 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 73 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 74 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 75 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 76 | END OF TERMS AND CONDITIONS 77 | 78 | APPENDIX: How to apply the Apache License to your work. 79 | 80 | To apply the Apache License to your work, attach the following 81 | boilerplate notice, with the fields enclosed by brackets "[]" 82 | replaced with your own identifying information. (Don't include 83 | the brackets!) The text should be enclosed in the appropriate 84 | comment syntax for the file format. We also recommend that a 85 | file or class name and description of purpose be included on the 86 | same "printed page" as the copyright notice for easier 87 | identification within third-party archives. 88 | 89 | Copyright [yyyy] [name of copyright owner] 90 | 91 | Licensed under the Apache License, Version 2.0 (the "License"); 92 | you may not use this file except in compliance with the License. 93 | You may obtain a copy of the License at 94 | 95 | http://www.apache.org/licenses/LICENSE-2.0 96 | 97 | Unless required by applicable law or agreed to in writing, software 98 | distributed under the License is distributed on an "AS IS" BASIS, 99 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 100 | See the License for the specific language governing permissions and 101 | limitations under the License. 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chef Knife Plugin for Oracle Cloud Infrastructure 2 | [![wercker status](https://app.wercker.com/status/82cc98510b6b52b3a37d1212174a1d56/s/master "wercker status")](https://app.wercker.com/project/byKey/82cc98510b6b52b3a37d1212174a1d56) 3 | 4 | ## About 5 | 6 | The knife-oci plugin allows users to interact with Oracle Cloud Infrastructure through [Chef Knife](https://docs.chef.io/knife.html). 7 | 8 | The project is open source and maintained by Oracle Corp. The home page for the project is [here](https://docs.us-phoenix-1.oraclecloud.com/Content/API/SDKDocs/knifeplugin.htm). 9 | 10 | **_Breaking Change Notice:_** Information about the recent name change from knife-bmcs to knife-oci can be found [here](docs/rename.md). 11 | 12 | ## Commands 13 | 14 | - Launch an OCI instance and bootstrap it as a Chef node: 15 | `knife oci server create` 16 | - List OCI compartments. 17 | `knife oci compartment list` 18 | - Delete an OCI instance: 19 | `knife oci server delete` 20 | - List OCI instances in a given compartment. **Note:** All instances in the compartment are returned, not only those that are Chef nodes: 21 | `knife oci server list` 22 | - List the images in a compartment: 23 | `knife oci image list` 24 | - List the VCNs in a compartment: 25 | `knife oci vcn list` 26 | - List the subnets in a VCN: 27 | `knife oci subnet list` 28 | - List the shapes that may be used for a particular image type: 29 | `knife oci shape list` 30 | - List the availability domains for your tenancy: 31 | `knife oci ad list` 32 | 33 | ## Installation 34 | 35 | Install from RubyGems with: 36 | 37 | chef gem install knife-oci 38 | 39 | Or: 40 | 41 | gem install knife-oci 42 | 43 | **Note:** The plugin depends on the OCI Ruby SDK. Information about that SDK can be found [here](https://docs.us-phoenix-1.oraclecloud.com/Content/API/SDKDocs/rubysdk.htm). 44 | 45 | ## Setup 46 | 47 | A config file is required to use Oracle Cloud Infrastructure commands. See the instructions for creating a config file [here](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm). 48 | 49 | By default, the config file will be loaded from ~/.oci/config. Alternate locations can be provided as an argument to each command using `--oci-config-file`, or as an entry in your knife.rb file. You can also specify a profile with `--oci-profile`. 50 | 51 | ## Setting the Compartment 52 | 53 | Most OCI commands require a compartment ID, which will default to the root compartment. If you do not have the correct permissions and you do not specify a different compartment, then you will receive an authorization error. 54 | 55 | A compartment ID can be provided with each OCI command using `--compartment-id`, or it can be provided in your knife.rb. If a compartment ID is set in both places, then the ID specified in the command will take precedence. 56 | 57 | ## Knife.rb values 58 | 59 | The following example shows the available knife.rb settings for the OCI Knife Plugin. All of these are optional. 60 | 61 | knife[:oci_config_file] = '~/.oci/my_alternate_config' 62 | knife[:oci_profile] = 'MY_ALTERNATE_PROFILE' 63 | knife[:compartment_id] = 'ocid1.compartment.oc1..aaaaaaaalsyenka3grgpvvmqwjshig5abzx3jnbvixxxzx373ehwdj7o5arc' 64 | 65 | ## Using the Server Create Command 66 | 67 | The following example shows how to launch and bootstrap an Oracle Linux image: 68 | 69 | knife oci server create 70 | --availability-domain 'kIdk:PHX-AD-1' 71 | --compartment-id 'ocidv1:tenancy:oc1:phx:1460406592660:aaaaaaaab4faofrfkxecohhjuivjq26a13' 72 | --image-id 'ocid1.image.oc1.phx.aaaaaaaaqutj4qjxihpl4mboabsa27mrpusygv6gurp47katabcvljmq3puq' 73 | --shape 'VM.Standard1.1' 74 | --subnet-id 'ocid1.subnet.oc1.phx.aaaaaaaaxlc5cv7ewqr343ms4lvcpxr4lznsf4cbs2565abcm23d3cfebrex' 75 | --ssh-authorized-keys-file ~/.keys/instance_keys.pub 76 | --display-name myinstance 77 | --identity-file ~/.keys/instance_keys 78 | --run-list 'recipe[my_cookbook::my_recipe]' 79 | --region us-phoenix-1 80 | 81 | When using the `knife oci server create` command, you must specify a public key using `--ssh-authorized-keys-file` and the corresponding private key using `--identity-file`. For more information, see [Managing Key Pairs on Linux Instances](https://docs.us-phoenix-1.oraclecloud.com/Content/Compute/Tasks/managingkeypairs.htm). 82 | 83 | Notes about the `knife oci server create` command: 84 | 85 | - All Oracle-provided Linux images are supported. Windows images are not supported at this time. 86 | - Bootstrapping is done through SSH only, and uses the public IP address. 87 | - For Ubuntu images, the user is usually 'ubuntu' instead of 'opc'. 88 | 89 | ## Help 90 | 91 | See the “Questions or Feedback?” section [here](https://docs.us-phoenix-1.oraclecloud.com/Content/API/SDKDocs/knifeplugin.htm). 92 | 93 | ## Changes 94 | 95 | See [CHANGELOG](/CHANGELOG.md). 96 | 97 | ## Contributing 98 | 99 | This project welcomes contributions from the community. Before submitting a pull request, please [review our contribution guide](./CONTRIBUTING.md) 100 | 101 | ## Security 102 | 103 | Please consult the [security guide](./SECURITY.md) for our responsible security vulnerability disclosure process 104 | 105 | ## Known Issues 106 | 107 | You can find information on any known issues with the SDK [here](https://docs.us-phoenix-1.oraclecloud.com/Content/knownissues.htm) and under the “Issues” tab of this GitHub repository. 108 | 109 | ## License 110 | 111 | Copyright (c) 2017, 2023 Oracle and/or its affiliates. All rights reserved. 112 | 113 | This SDK and sample is dual licensed under the Universal Permissive License 1.0 and the Apache License 2.0. 114 | 115 | See [LICENSE](/LICENSE.txt) for more details. 116 | -------------------------------------------------------------------------------- /README_BMCS.md: -------------------------------------------------------------------------------- 1 | # Chef Knife Plugin for Oracle BMCS 2 | 3 | ## About 4 | 5 | The knife-bmcs plugin for Chef Knife has been deprecated. Please use knife-oci instead. Details about the change can be found [here](docs/rename.md), and the main README is [here](/README.md). 6 | 7 | ## Commands 8 | 9 | - Launch a BMCS instance and bootstrap it as a Chef node: 10 | `knife bmcs server create` 11 | - List BMCS compartments. 12 | `knife bmcs compartment list` 13 | - Delete a BMCS instance: 14 | `knife bmcs server delete` 15 | - List BMCS instances in a given compartment. **Note:** All instances in the compartment are returned, not only those that are Chef nodes: 16 | `knife bmcs server list` 17 | - List the images in a compartment: 18 | `knife bmcs image list` 19 | - List the VCNs in a compartment: 20 | `knife bmcs vcn list` 21 | - List the subnets in a VCN: 22 | `knife bmcs subnet list` 23 | - List the shapes that may be used for a particular image type: 24 | `knife bmcs shape list` 25 | - List the availability domains for your tenancy: 26 | `knife bmcs ad list` 27 | 28 | ## Requirements 29 | 30 | Linux and Mac are supported. Windows is not supported at this time. 31 | 32 | ## Installation 33 | 34 | Install from RubyGems with: 35 | 36 | chef gem install knife-bmcs 37 | 38 | Or: 39 | 40 | gem install knife-bmcs 41 | 42 | **Note:** The plugin depends on the Oracle BMCS Ruby SDK. Information about that SDK can be found [here](https://docs.us-phoenix-1.oraclecloud.com/Content/API/SDKDocs/rubysdk.htm). 43 | 44 | ## Setup 45 | 46 | A config file is required to use Bare Metal Cloud Services commands. See the instructions for creating a config file [here](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm). 47 | 48 | By default, the config file will be loaded from ~/.oraclebmc/config. Alternate locations can be provided as an argument to each command using `--bmcs-config-file`, or as an entry in your knife.rb file. You can also specify a profile with `--bmcs-profile`. 49 | 50 | ## Setting the Compartment 51 | 52 | Most BMCS commands require a compartment ID, which will default to the root compartment. If you do not have the correct permissions and you do not specify a different compartment, then you will receive an authorization error. 53 | 54 | A compartment ID can be provided with each BMCS command using `--compartment-id`, or it can be provided in your knife.rb. If a compartment ID is set in both places, then the ID specified in the command will take precedence. 55 | 56 | ## Knife.rb values 57 | 58 | The following example shows the available knife.rb settings for the BMCS Knife Plugin. All of these are optional. 59 | 60 | knife[:bmcs_config_file] = '~/.oraclebmc/my_alternate_config' 61 | knife[:bmcs_profile] = 'MY_ALTERNATE_PROFILE' 62 | knife[:compartment_id] = 'ocid1.compartment.oc1..aaaaaaaalsyenka3grgpvvmqwjshig5abzx3jnbvixxxzx373ehwdj7o5arc' 63 | 64 | ## Using the Server Create Command 65 | 66 | The following example shows how to launch and bootstrap an Oracle Linux image: 67 | 68 | knife bmcs server create 69 | --availability-domain 'kIdk:PHX-AD-1' 70 | --compartment-id 'ocidv1:tenancy:oc1:phx:1460406592660:aaaaaaaab4faofrfkxecohhjuivjq26a13' 71 | --image-id 'ocid1.image.oc1.phx.aaaaaaaaqutj4qjxihpl4mboabsa27mrpusygv6gurp47katabcvljmq3puq' 72 | --shape 'VM.Standard1.1' 73 | --subnet-id 'ocid1.subnet.oc1.phx.aaaaaaaaxlc5cv7ewqr343ms4lvcpxr4lznsf4cbs2565abcm23d3cfebrex' 74 | --ssh-authorized-keys-file ~/.keys/instance_keys.pub 75 | --display-name myinstance 76 | --identity-file ~/.keys/instance_keys 77 | --run-list 'recipe[my_cookbook::my_recipe]' 78 | --region us-phoenix-1 79 | 80 | When using the `knife bmcs server create` command, you must specify a public key using `--ssh-authorized-keys-file` and the corresponding private key using `--identity-file`. For more information, see [Managing Key Pairs on Linux Instances](https://docs.us-phoenix-1.oraclecloud.com/Content/Compute/Tasks/managingkeypairs.htm). 81 | 82 | Notes about the `knife bmcs server create` command: 83 | 84 | - All Oracle-provided Linux images are supported. Windows images are not supported at this time. 85 | - Bootstrapping is done through SSH only, and uses the public IP address. 86 | - For Ubuntu images, the user is usually 'ubuntu' instead of 'opc'. 87 | 88 | ## Help 89 | 90 | See the “Questions or Feedback?” section [here](https://docs.us-phoenix-1.oraclecloud.com/Content/API/SDKDocs/knifeplugin.htm). 91 | 92 | ## Changes 93 | 94 | See [CHANGELOG](/CHANGELOG.md). 95 | 96 | ## Contributing 97 | 98 | knife-bmcs is an open source project. See [CONTRIBUTING](/CONTRIBUTING.md) for details. 99 | 100 | Oracle gratefully acknowledges the contributions to knife-bmcs that have been made by the community. 101 | 102 | ## Known Issues 103 | 104 | You can find information on any known issues with the SDK [here](https://docs.us-phoenix-1.oraclecloud.com/Content/knownissues.htm) and under the “Issues” tab of this GitHub repository. 105 | 106 | ## License 107 | 108 | Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 109 | 110 | This SDK and sample is dual licensed under the Universal Permissive License 1.0 and the Apache License 2.0. 111 | 112 | See [LICENSE](/LICENSE.txt) for more details. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'rubocop/rake_task' 4 | require 'rspec/core/rake_task' 5 | 6 | RuboCop::RakeTask.new 7 | 8 | task :install do 9 | sh %(chef gem build knife-oci.gemspec) 10 | sh %(chef gem install knife-oci-*.gem) 11 | end 12 | 13 | desc 'Run all unit tests.' 14 | RSpec::Core::RakeTask.new(:unit) do |t| 15 | t.pattern = 'spec/unit/**/*_spec.rb' 16 | end 17 | 18 | desc 'Run all integration tests.' 19 | RSpec::Core::RakeTask.new(:integ) do |t| 20 | t.pattern = 'spec/integration/**/*_spec.rb' 21 | end 22 | 23 | task default: %i[unit rubocop] 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting security vulnerabilities 2 | 3 | Oracle values the independent security research community and believes that 4 | responsible disclosure of security vulnerabilities helps us ensure the security 5 | and privacy of all our users. 6 | 7 | Please do NOT raise a GitHub Issue to report a security vulnerability. If you 8 | believe you have found a security vulnerability, please submit a report to 9 | [secalert_us@oracle.com][1] preferably with a proof of concept. Please review 10 | some additional information on [how to report security vulnerabilities to Oracle][2]. 11 | We encourage people who contact Oracle Security to use email encryption using 12 | [our encryption key][3]. 13 | 14 | We ask that you do not use other channels or contact the project maintainers 15 | directly. 16 | 17 | Non-vulnerability related security issues including ideas for new or improved 18 | security features are welcome on GitHub Issues. 19 | 20 | ## Security updates, alerts and bulletins 21 | 22 | Security updates will be released on a regular cadence. Many of our projects 23 | will typically release security fixes in conjunction with the 24 | [Oracle Critical Patch Update][3] program. Additional 25 | information, including past advisories, is available on our [security alerts][4] 26 | page. 27 | 28 | ## Security-related information 29 | 30 | We will provide security related information such as a threat model, considerations 31 | for secure use, or any known security issues in our documentation. Please note 32 | that labs and sample code are intended to demonstrate a concept and may not be 33 | sufficiently hardened for production use. 34 | 35 | [1]: mailto:secalert_us@oracle.com 36 | [2]: https://www.oracle.com/corporate/security-practices/assurance/vulnerability/reporting.html 37 | [3]: https://www.oracle.com/security-alerts/encryptionkey.html 38 | [4]: https://www.oracle.com/security-alerts/ 39 | -------------------------------------------------------------------------------- /docs/rename.md: -------------------------------------------------------------------------------- 1 | ## Notes on the OCI Rename 2 | 3 | The name Bare Metal Cloud Services (BMCS) has changed to Oracle Cloud Infrastructure (OCI), and this plugin has updated all occurrences of the old name accordingly. Existing versions of knife-bmcs will not be affected, but no new versions of knife-bmcs will be released, so users are encouraged to move to the new knife-oci plugin to take advantage of new features and bug fixes going forward. The new plugin will require the following changes for existing users of knife-bmcs: 4 | 5 | * All commands will be under 'knife oci' instead of 'knife bmcs'. 6 | * The parameters '--bmcs-config-file' and '--bmcs-profile' will change to '--oci-config-file' and '--oci-profile'. 7 | * If 'bmcs_config_file' or 'bmcs_profile' is used in your knife config file, then these should be changed to 'oci_config_file' and 'oci_profile'. However, for backwards compatibility, if the 'oci_' parameters are not found then the BMCS versions will be used. 8 | * The default location for the the config file will move from '~/.oraclebmc/config' to '~/.oci/config'. For backwards compatibility, if '~/.oci/config' is not found then '~/.oraclebmc/config' will be used. 9 | 10 | The knife-oci plugin can be installed from RubyGems with: 11 | 12 | chef gem install knife-oci 13 | 14 | Or: 15 | 16 | gem install knife-oci 17 | 18 | Existing users of knife-bmcs may still view the documentation for that version at [README_BMCS.md](/README_BMCS.md). 19 | -------------------------------------------------------------------------------- /knife-oci.gemspec: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib')) 4 | require 'knife-oci/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = 'knife-oci' 8 | s.version = Knife::OCI::VERSION 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ['Oracle'] 11 | s.email = ['brian.gustafson@oracle.com', 'joe.levy@oracle.com'] 12 | s.homepage = 'https://github.com/oracle/knife-oci' 13 | s.summary = 'Chef Knife Plugin for Oracle Cloud Infrastructure' 14 | s.description = '' 15 | s.licenses = ['UPL-1.0', 'Apache-2.0'] 16 | 17 | s.required_ruby_version = '>= 2.2.0' 18 | 19 | s.add_runtime_dependency 'oci', '~> 2.0', '>= 2.0.0' 20 | 21 | s.require_paths = ['lib'] 22 | s.files = Dir['./lib/**/*.rb', 'LICENSE.txt'] 23 | end 24 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_ad_list.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_common_options' 5 | require 'chef/knife/oci_helper' 6 | 7 | class Chef 8 | class Knife 9 | # List availability domains 10 | class OciAdList < Knife 11 | banner 'knife oci ad list (options)' 12 | 13 | include OciHelper 14 | include OciCommonOptions 15 | 16 | deps do 17 | require 'oci' 18 | end 19 | 20 | def run 21 | options = {} 22 | columns = [] 23 | 24 | list_for_display, last_response = get_display_results(options) do |client_options| 25 | response = identity_client.list_availability_domains(compartment_id, client_options) 26 | 27 | items = response_to_list(response) do |item| 28 | [item.name] 29 | end 30 | [response, items] 31 | end 32 | 33 | display_list_from_array(list_for_display, columns.length) 34 | warn_if_page_is_truncated(last_response) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_common_options.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'knife-oci/version' 5 | 6 | class Chef 7 | class Knife 8 | # Options that should be included in all OCI commands 9 | module OciCommonOptions 10 | def self.included(includer) 11 | includer.class_eval do 12 | option :region, 13 | long: '--region REGION', 14 | description: 'The region to make calls against. (e.g., `us-ashburn-1`)' 15 | 16 | option :oci_config_file, 17 | long: '--oci-config-file FILE', 18 | description: 'The path to the OCI config file. Default: ~/.oci/config' 19 | 20 | option :oci_profile, 21 | long: '--oci-profile PROFILE', 22 | description: 'The profile to load from the OCI config file. Default: DEFAULT' 23 | end 24 | # all commands except compartment list get a compartment-id option 25 | return if includer.to_s == 'Chef::Knife::OciCompartmentList' 26 | includer.class_eval do 27 | option :compartment_id, 28 | long: '--compartment-id COMPARTMENT', 29 | description: 'The OCID of the compartment.' 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_compartment_list.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_common_options' 5 | require 'chef/knife/oci_helper' 6 | 7 | class Chef 8 | class Knife 9 | # List OCI compartments 10 | class OciCompartmentList < Knife 11 | banner 'knife oci compartment list (options)' 12 | 13 | include OciHelper 14 | include OciCommonOptions 15 | 16 | deps do 17 | require 'oci' 18 | end 19 | 20 | option :limit, 21 | long: '--limit LIMIT', 22 | description: 'The maximum number of items to return.' 23 | 24 | def run 25 | options = {} 26 | options[:limit] = config[:limit] if config[:limit] 27 | 28 | columns = ['Display Name', 'ID'] 29 | 30 | list_for_display = config[:format] == 'summary' ? bold(columns) : [] 31 | list_data, last_response = get_display_results(options) do |client_options| 32 | response = identity_client.list_compartments(oci_config.tenancy, client_options) 33 | 34 | items = response_to_list(response) do |item| 35 | [item.name, item.id] 36 | end 37 | [response, items] 38 | end 39 | list_for_display += list_data 40 | 41 | display_list_from_array(list_for_display, columns.length) 42 | warn_if_page_is_truncated(last_response) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_helper.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'knife-oci/version' 5 | 6 | # rubocop:disable Metrics/ModuleLength 7 | class Chef 8 | class Knife 9 | # OCI helper module 10 | module OciHelper 11 | def oci_config 12 | unless @oci_config 13 | @oci_config = OCI::ConfigFileLoader.load_config(config_file_location: config_file_location, profile_name: config_file_profile) 14 | @oci_config.region = config[:region] if config[:region] 15 | 16 | @oci_config.additional_user_agent = "Oracle-ChefKnifeOCI/#{::Knife::OCI::VERSION}" 17 | end 18 | 19 | @oci_config 20 | end 21 | 22 | def config_file_location 23 | # Load first from command line args if available, then from knife.rb, then use the default. 24 | # For backwards compatibility with knife-bmcs, if oci version is not found in knife.rb, then check for bmcs version. 25 | config[:oci_config_file] || Chef::Config[:knife][:oci_config_file] || Chef::Config[:knife][:bmcs_config_file] || OCI::ConfigFileLoader::DEFAULT_CONFIG_FILE 26 | end 27 | 28 | def config_file_profile 29 | # Load first from command line args if available, then from knife.rb, then use the default. 30 | # For backwards compatibility with knife-bmcs, if oci version is not found in knife.rb, then check for bmcs version. 31 | config[:oci_profile] || Chef::Config[:knife][:oci_profile] || Chef::Config[:knife][:bmcs_profile] || OCI::ConfigFileLoader::DEFAULT_PROFILE 32 | end 33 | 34 | def compute_client 35 | @compute_client ||= OCI::Core::ComputeClient.new(config: oci_config) 36 | end 37 | 38 | def network_client 39 | @network_client ||= OCI::Core::VirtualNetworkClient.new(config: oci_config) 40 | end 41 | 42 | def identity_client 43 | @identity_client ||= OCI::Identity::IdentityClient.new(config: oci_config) 44 | end 45 | 46 | # Get the compartment ID first from the command line args if available, then from the knife.rb 47 | # file, and if neither of those is specified then use the tenancy. 48 | def compartment_id 49 | @compartment_id ||= config[:compartment_id] || Chef::Config[:knife][:compartment_id] || oci_config.tenancy 50 | end 51 | 52 | def error_and_exit(message) 53 | ui.error message 54 | exit(1) 55 | end 56 | 57 | def validate_required_params(required_params, params) 58 | missing_params = required_params.select do |param| 59 | params[param].nil? 60 | end 61 | 62 | error_and_exit("Missing the following required parameters: #{missing_params.join(', ').tr('_', '-')}") unless missing_params.empty? 63 | end 64 | 65 | def warn_if_page_is_truncated(response) 66 | ui.warn('This list has been truncated. To view more items, increase the limit.') if response.headers.include? 'opc-next-page' 67 | end 68 | 69 | def next_page_token(response) 70 | return response.headers['opc-next-page'] if response.headers.include? 'opc-next-page' 71 | nil 72 | end 73 | 74 | def get_display_results(options) 75 | max_results = config[:limit] ? Integer(config[:limit]) : nil 76 | 77 | num_fetched_results = 0 78 | list_for_display = [] 79 | response = nil 80 | loop do 81 | response, new_items = yield(options) 82 | 83 | list_for_display += new_items 84 | num_fetched_results += response.data.length if response.data 85 | break if next_page_token(response).nil? 86 | break if max_results && num_fetched_results >= max_results 87 | options[:page] = next_page_token(response) 88 | options[:limit] = (max_results - num_fetched_results).to_s if max_results 89 | end 90 | [list_for_display, response] 91 | end 92 | 93 | def bold(list) 94 | bolded_list = [] 95 | list.each do |column| 96 | bolded_list += [ui.color(column, :bold)] 97 | end 98 | bolded_list.flatten.compact 99 | end 100 | 101 | # Return data in summary mode format 102 | def _summary_list(list) 103 | list_for_display = [] 104 | 105 | if list 106 | list.each do |item| 107 | display_item = yield(item, list_for_display) 108 | list_for_display += display_item if display_item 109 | end 110 | end 111 | 112 | list_for_display 113 | end 114 | 115 | # Return data in non-summary mode format. 116 | def _non_summary_list(list) 117 | list_for_display = [] 118 | list.each do |item| 119 | list_for_display += [item.to_hash] 120 | end 121 | 122 | list_for_display 123 | end 124 | 125 | # Return a one dimensional array of data based on API response. 126 | # Result is compatible with display_list_from_array. 127 | def response_to_list(response, &block) 128 | list = if response.data.nil? 129 | [] 130 | else 131 | response.data.is_a?(Array) ? response.data : [response.data] 132 | end 133 | 134 | return _summary_list(list, &block) if config[:format] == 'summary' 135 | _non_summary_list(list) 136 | end 137 | 138 | # Display a list using a one dimensional array as input 139 | # 140 | # Example output in summary mode: 141 | # display_list_from_array(['a','b', 'c', 'd'], 2) 142 | # a b 143 | # c d 144 | def display_list_from_array(list_for_display, num_columns) 145 | if config[:format] == 'summary' 146 | num_columns = 1 if num_columns < 1 147 | puts ui.list(list_for_display, :uneven_columns_across, num_columns) 148 | else 149 | ui.output(list_for_display) 150 | end 151 | end 152 | 153 | # Return a true or false with the confirmation result. 154 | # Note: user prompt is bypassed with --yes to confirm automatically. 155 | def confirm(prompt) 156 | return true if config[:yes] 157 | valid_responses = %w[yes no y n] 158 | response = nil 159 | 3.times do 160 | response = ui.ask(prompt).downcase 161 | break if valid_responses.include? response 162 | ui.warn "Valid responses are #{valid_responses}" 163 | end 164 | response.match(/^y/) 165 | end 166 | 167 | def check_can_access_instance(instance_id) 168 | response = compute_client.get_instance(instance_id) 169 | error_and_exit 'Instance is already in terminated state' if response && response.data && response.data.lifecycle_state == OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATED 170 | rescue OCI::Errors::ServiceError => service_error 171 | raise unless service_error.service_code == 'NotAuthorizedOrNotFound' 172 | error_and_exit 'Instance not authorized or not found' 173 | else 174 | return response 175 | end 176 | end 177 | end 178 | end 179 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_helper_show.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'knife-oci/version' 5 | 6 | # Methods to extend the instance model 7 | module ServerDetails 8 | attr_accessor :compartment_name 9 | attr_accessor :image_name 10 | attr_accessor :launchtime 11 | attr_accessor :vcn_id 12 | attr_accessor :vcn_name 13 | end 14 | 15 | # Methods to extend the vnic model 16 | module VnicDetails 17 | attr_accessor :fqdn 18 | attr_accessor :subnet_name 19 | attr_accessor :vcn_id 20 | end 21 | 22 | class Chef 23 | class Knife 24 | # Utility routines to fill out data for 'server show' functionality 25 | module OciHelperShow 26 | def lookup_compartment_name(compartment_id) 27 | compartment = identity_client.get_compartment(compartment_id, {}) 28 | rescue OCI::Errors::ServiceError => service_error 29 | raise unless service_error.service_code == 'NotAuthorizedOrNotFound' 30 | else 31 | compartment.data.name 32 | end 33 | 34 | def lookup_image_name(image_id) 35 | image = compute_client.get_image(image_id, {}) 36 | rescue OCI::Errors::ServiceError => service_error 37 | raise unless service_error.service_code == 'NotAuthorizedOrNotFound' 38 | else 39 | image.data.display_name 40 | end 41 | 42 | def lookup_vcn_name(vcn_id) 43 | vcn = network_client.get_vcn(vcn_id, {}) 44 | rescue OCI::Errors::ServiceError => service_error 45 | raise unless service_error.service_code == 'NotAuthorizedOrNotFound' 46 | else 47 | vcn.data.display_name 48 | end 49 | 50 | def add_server_details(server, vcn_id) 51 | server.extend ServerDetails 52 | 53 | server.launchtime = server.time_created.strftime('%a, %e %b %Y %T %Z') 54 | server.compartment_name = lookup_compartment_name(server.compartment_id) 55 | server.image_name = lookup_image_name(server.image_id) 56 | server.vcn_id = vcn_id 57 | server.vcn_name = lookup_vcn_name(vcn_id) 58 | end 59 | 60 | def add_vnic_details(vnic) 61 | vnic.extend VnicDetails 62 | 63 | begin 64 | subnet = network_client.get_subnet(vnic.subnet_id, {}) 65 | rescue OCI::Errors::ServiceError => service_error 66 | raise unless service_error.service_code == 'NotAuthorizedOrNotFound' 67 | else 68 | vnic.fqdn = vnic.hostname_label + '.' + subnet.data.subnet_domain_name if 69 | subnet.data && subnet.data.subnet_domain_name && vnic.hostname_label 70 | vnic.subnet_name = subnet.data.display_name if 71 | subnet.data && subnet.data.display_name 72 | # piggyback the vcn_id from here, so we can avoid a few network calls 73 | vnic.vcn_id = subnet.data.vcn_id 74 | end 75 | end 76 | 77 | def show_value(key, value, color = :cyan) 78 | ui.msg "#{ui.color(key, color)}: #{value}" if value && !value.to_s.empty? 79 | end 80 | 81 | # rubocop:disable Metrics/CyclomaticComplexity 82 | def display_server_info(config, instance, vnics) 83 | show_value('Display Name', instance.display_name) 84 | show_value('Instance ID', instance.id) 85 | show_value('Lifecycle State', instance.lifecycle_state) 86 | show_value('Availability Domain', instance.availability_domain) 87 | show_value('Compartment Name', instance.compartment_name) if instance.respond_to? :compartment_name 88 | show_value('Compartment ID', instance.compartment_id) 89 | show_value('Region', instance.region) 90 | show_value('Image Name', instance.image_name) if instance.respond_to? :image_name 91 | show_value('Image ID', instance.image_id) 92 | show_value('Shape', instance.shape) 93 | show_value('VCN Name', instance.vcn_name) if instance.respond_to? :vcn_name 94 | show_value('VCN ID', instance.vcn_id) if instance.respond_to? :vcn_id 95 | show_value('Launched', instance.launchtime) if instance.respond_to? :launchtime 96 | vnics.each_index do |index| 97 | prefix = vnics[index].is_primary ? 'Primary' : 'Secondary' 98 | show_value("#{prefix} Public IP Address", vnics[index].public_ip) 99 | show_value("#{prefix} Private IP Address", vnics[index].private_ip) 100 | show_value("#{prefix} Hostname", vnics[index].hostname_label) 101 | show_value("#{prefix} FQDN", vnics[index].fqdn) if vnics[index].respond_to? :fqdn 102 | show_value("#{prefix} Subnet Name", vnics[index].subnet_name) if vnics[index].respond_to? :subnet_name 103 | end 104 | show_value('Node Name', config[:chef_node_name]) 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_image_list.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_common_options' 5 | require 'chef/knife/oci_helper' 6 | 7 | class Chef 8 | class Knife 9 | # List available images 10 | class OciImageList < Knife 11 | banner 'knife oci image list (options)' 12 | 13 | include OciHelper 14 | include OciCommonOptions 15 | 16 | deps do 17 | require 'oci' 18 | end 19 | 20 | option :limit, 21 | long: '--limit LIMIT', 22 | description: 'The maximum number of items to return.' 23 | 24 | def run 25 | options = {} 26 | options[:limit] = config[:limit] if config[:limit] 27 | 28 | columns = ['Display Name', 'ID', 'OS', 'OS Version'] 29 | 30 | list_for_display = config[:format] == 'summary' ? bold(columns) : [] 31 | list_data, last_response = get_display_results(options) do |client_options| 32 | response = compute_client.list_images(compartment_id, client_options) 33 | 34 | items = response_to_list(response) do |image| 35 | [image.display_name, image.id, image.operating_system, image.operating_system_version] 36 | end 37 | [response, items] 38 | end 39 | list_for_display += list_data 40 | 41 | display_list_from_array(list_for_display, columns.length) 42 | warn_if_page_is_truncated(last_response) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_server_create.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_helper' 5 | require 'chef/knife/oci_helper_show' 6 | require 'chef/knife/oci_common_options' 7 | 8 | class Chef 9 | class Knife 10 | # Server Create Command: Launch an instance and bootstrap it. 11 | class OciServerCreate < Knife 12 | banner 'knife oci server create (options)' 13 | 14 | include OciHelper 15 | include OciHelperShow 16 | include OciCommonOptions 17 | 18 | # Port for SSH - might want to parameterize this in the future. 19 | SSH_PORT = 22 20 | 21 | WAIT_FOR_SSH_INTERVAL_SECONDS = 2 22 | DEFAULT_WAIT_FOR_SSH_MAX_SECONDS = 300 23 | DEFAULT_WAIT_TO_STABILIZE_SECONDS = 40 24 | 25 | deps do 26 | require 'oci' 27 | require 'chef/knife/bootstrap' 28 | Chef::Knife::Bootstrap.load_deps 29 | end 30 | 31 | option :availability_domain, 32 | long: '--availability-domain AD', 33 | description: 'The Availability Domain of the instance. (required)' 34 | 35 | option :display_name, 36 | long: '--display-name NAME', 37 | description: "A user-friendly name for the instance. Does not have to be unique, and it's changeable." 38 | 39 | option :hostname_label, 40 | long: '--hostname-label HOSTNAME', 41 | description: 'The hostname for the VNIC that is created during instance launch. Used for DNS. The value is the hostname '\ 42 | "portion of the instance's fully qualified domain name (FQDN). Must be unique across all VNICs in the subnet "\ 43 | 'and comply with RFC 952 and RFC 1123. The value cannot be changed, and it can be retrieved from the Vnic object.' 44 | 45 | option :image_id, 46 | long: '--image-id IMAGE', 47 | description: 'The OCID of the image used to boot the instance. (required)' 48 | 49 | option :metadata, 50 | long: '--metadata METADATA', 51 | description: 'Custom metadata key/value pairs in JSON format.' 52 | 53 | option :shape, 54 | long: '--shape SHAPE', 55 | description: 'The shape of an instance. The shape determines the number of CPUs, amount of memory, and other resources allocated to the instance. (required)' 56 | 57 | option :ssh_authorized_keys_file, 58 | long: '--ssh-authorized-keys-file FILE', 59 | description: 'A file containing one or more public SSH keys to be included in the ~/.ssh/authorized_keys file for the default user on the instance. '\ 60 | 'Use a newline character to separate multiple keys. The SSH keys must be in the format necessary for the authorized_keys file. This parameter '\ 61 | "is a convenience wrapper around the 'ssh_authorized_keys' field of the --metadata parameter. Populating both values in the same call will result "\ 62 | 'in an error. For more info see documentation: https://docs.us-phoenix-1.oraclecloud.com/api/#/en/iaas/20160918/requests/LaunchInstanceDetails. (required)' 63 | 64 | option :subnet_id, 65 | long: '--subnet-id SUBNET', 66 | description: 'The OCID of the subnet. (required)' 67 | 68 | option :user_data_file, 69 | long: '--user-data-file FILE', 70 | description: 'A file containing data that Cloud-Init can use to run custom scripts or provide custom Cloud-Init configuration. This parameter is a convenience '\ 71 | "wrapper around the 'user_data' field of the --metadata parameter. Populating both values in the same call will result in an error. For more info "\ 72 | 'see Cloud-Init documentation: https://cloudinit.readthedocs.org/en/latest/topics/format.html.' 73 | 74 | option :ssh_user, 75 | short: '-x USERNAME', 76 | long: '--ssh-user USERNAME', 77 | description: 'The SSH username. Defaults to opc.', 78 | default: 'opc' 79 | 80 | option :ssh_password, 81 | short: '-P PASSWORD', 82 | long: '--ssh-password PASSWORD', 83 | description: 'The SSH password' 84 | 85 | option :identity_file, 86 | short: '-i FILE', 87 | long: '--identity-file IDENTITY_FILE', 88 | description: 'The SSH identity file used for authentication. This must correspond to a public SSH key provided by --ssh-authorized-keys-file. (required)' 89 | 90 | option :chef_node_name, 91 | short: '-N NAME', 92 | long: '--node-name NAME', 93 | description: 'The Chef node name for the new node. If not specified, the instance display name will be used.' 94 | 95 | option :run_list, 96 | short: '-r RUN_LIST', 97 | long: '--run-list RUN_LIST', 98 | description: 'A comma-separated list of roles or recipes.', 99 | proc: ->(o) { o.split(/[\s,]+/) }, 100 | default: [] 101 | 102 | option :wait_to_stabilize, 103 | long: '--wait-to-stabilize SECONDS', 104 | description: "Duration to pause after SSH becomes reachable. Default: #{DEFAULT_WAIT_TO_STABILIZE_SECONDS}" 105 | 106 | option :wait_for_ssh_max, 107 | long: '--wait-for-ssh-max SECONDS', 108 | description: "The maximum time to wait for SSH to become reachable. Default: #{DEFAULT_WAIT_FOR_SSH_MAX_SECONDS}" 109 | 110 | def run 111 | $stdout.sync = true 112 | validate_required_params(%i[availability_domain image_id shape subnet_id identity_file ssh_authorized_keys_file], config) 113 | validate_wait_options 114 | 115 | metadata = merge_metadata 116 | error_and_exit 'SSH authorized keys must be specified.' unless metadata['ssh_authorized_keys'] 117 | 118 | request = OCI::Core::Models::LaunchInstanceDetails.new 119 | request.availability_domain = config[:availability_domain] 120 | request.compartment_id = compartment_id 121 | request.display_name = config[:display_name] 122 | request.hostname_label = config[:hostname_label] 123 | request.image_id = config[:image_id] 124 | request.metadata = metadata 125 | request.shape = config[:shape] 126 | request.subnet_id = config[:subnet_id] 127 | 128 | response = compute_client.launch_instance(request) 129 | instance = response.data 130 | 131 | ui.msg "Launched instance '#{instance.display_name}' [#{instance.id}]" 132 | instance = wait_for_instance_running(instance.id) 133 | ui.msg "Instance '#{instance.display_name}' is now running." 134 | 135 | vnic = get_vnic(instance.id, instance.compartment_id) 136 | 137 | unless wait_for_ssh(vnic.public_ip, SSH_PORT, WAIT_FOR_SSH_INTERVAL_SECONDS, config[:wait_for_ssh_max]) 138 | error_and_exit 'Timed out while waiting for SSH access.' 139 | end 140 | 141 | wait_to_stabilize 142 | 143 | config[:chef_node_name] = instance.display_name unless config[:chef_node_name] 144 | 145 | ui.msg "Bootstrapping with node name '#{config[:chef_node_name]}'." 146 | 147 | # TODO: Consider adding a use_private_ip option. 148 | bootstrap(vnic.public_ip) 149 | 150 | ui.msg "Created and bootstrapped node '#{config[:chef_node_name]}'." 151 | ui.msg "\n" 152 | 153 | add_vnic_details(vnic) 154 | add_server_details(instance, vnic.vcn_id) 155 | 156 | display_server_info(config, instance, [vnic]) 157 | end 158 | 159 | def bootstrap(name) 160 | bootstrap = Chef::Knife::Bootstrap.new 161 | 162 | bootstrap.name_args = [name] 163 | bootstrap.config[:chef_node_name] = config[:chef_node_name] 164 | bootstrap.config[:ssh_user] = config[:ssh_user] 165 | bootstrap.config[:ssh_password] = config[:ssh_password] 166 | bootstrap.config[:identity_file] = config[:identity_file] 167 | bootstrap.config[:use_sudo] = true 168 | bootstrap.config[:run_list] = config[:run_list] 169 | 170 | bootstrap.config[:yes] = true if config[:yes] 171 | 172 | bootstrap.run 173 | end 174 | 175 | def validate_wait_option(p, default) 176 | arg_name = "--#{p.to_s.tr('_', '-')}" 177 | config[p] = config[p].to_s.empty? ? default : Integer(config[p]) 178 | error_and_exit "#{arg_name} must be 0 or greater" if config[p] < 0 179 | rescue 180 | error_and_exit "#{arg_name} must be numeric" 181 | end 182 | 183 | def validate_wait_options 184 | validate_wait_option(:wait_to_stabilize, DEFAULT_WAIT_TO_STABILIZE_SECONDS) 185 | validate_wait_option(:wait_for_ssh_max, DEFAULT_WAIT_FOR_SSH_MAX_SECONDS) 186 | end 187 | 188 | def wait_to_stabilize 189 | # This extra sleep even after getting SSH access is necessary. It's not clear why, but without it we often get 190 | # errors about missing a password for ssh, or sometimes errors during bootstrapping. (Note that plugins for other 191 | # cloud providers have similar sleeps.) 192 | Kernel.sleep(config[:wait_to_stabilize]) 193 | end 194 | 195 | def wait_for_ssh(hostname, ssh_port, interval_seconds, max_time_seconds) 196 | print ui.color('Waiting for ssh access...', :magenta) 197 | 198 | end_time = Time.now + max_time_seconds 199 | 200 | begin 201 | while Time.now < end_time 202 | return true if can_ssh(hostname, ssh_port) 203 | 204 | show_progress 205 | sleep interval_seconds 206 | end 207 | ensure 208 | end_progress_indicator 209 | end 210 | 211 | false 212 | end 213 | 214 | def can_ssh(hostname, ssh_port) 215 | socket = TCPSocket.new(hostname, ssh_port) 216 | # Wait up to 5 seconds. 217 | readable = IO.select([socket], nil, nil, 5) 218 | if readable 219 | content = socket.gets 220 | # Make sure some content was actually returned. 221 | return true unless content.nil? || content.empty? 222 | else 223 | false 224 | end 225 | rescue SocketError, IOError, Errno::ETIMEDOUT, Errno::EPERM, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ECONNRESET, Errno::ENOTCONN 226 | false 227 | ensure 228 | socket && socket.close 229 | end 230 | 231 | def wait_for_instance_running(instance_id) 232 | print ui.color('Waiting for instance to reach running state...', :magenta) 233 | 234 | begin 235 | response = compute_client.get_instance(instance_id).wait_until(:lifecycle_state, 236 | OCI::Core::Models::Instance::LIFECYCLE_STATE_RUNNING, 237 | max_interval_seconds: 3) do |poll_response| 238 | if poll_response.data.lifecycle_state == OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATED || 239 | poll_response.data.lifecycle_state == OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATING 240 | throw :stop_succeed 241 | end 242 | 243 | show_progress 244 | end 245 | ensure 246 | end_progress_indicator 247 | end 248 | 249 | if response.data.lifecycle_state != OCI::Core::Models::Instance::LIFECYCLE_STATE_RUNNING 250 | error_and_exit 'Instance failed to provision.' 251 | end 252 | 253 | response.data 254 | end 255 | 256 | # Return the first VNIC found (which should be the only VNIC). 257 | def get_vnic(instance_id, compartment) 258 | compute_client.list_vnic_attachments(compartment, instance_id: instance_id).each do |response| 259 | response.data.each do |vnic_attachment| 260 | return network_client.get_vnic(vnic_attachment.vnic_id).data 261 | end 262 | end 263 | end 264 | 265 | def merge_metadata 266 | metadata = config[:metadata] 267 | 268 | if metadata 269 | begin 270 | metadata = JSON.parse(metadata) 271 | rescue JSON::ParserError 272 | error_and_exit('Metadata value must be in JSON format. Example: \'{"key1":"value1", "key2":"value2"}\'') 273 | end 274 | else 275 | metadata = {} 276 | end 277 | 278 | ssh_authorized_keys = get_file_content(:ssh_authorized_keys_file) 279 | user_data = get_file_content(:user_data_file) 280 | user_data = Base64.strict_encode64(user_data) if user_data 281 | 282 | if ssh_authorized_keys 283 | error_and_exit('Cannot specify ssh-authorized-keys as part of both --ssh-authorized-keys-file and --metadata.') if metadata.key? 'ssh_authorized_keys' 284 | metadata['ssh_authorized_keys'] = ssh_authorized_keys 285 | end 286 | 287 | if user_data 288 | error_and_exit('Cannot specify CloudInit user-data as part of both --user-data-file and --metadata.') if metadata.key? 'user_data' 289 | metadata['user_data'] = user_data 290 | end 291 | 292 | metadata 293 | end 294 | 295 | def show_progress 296 | print ui.color('.', :magenta) 297 | $stdout.flush 298 | end 299 | 300 | def end_progress_indicator 301 | print ui.color("done\n", :magenta) 302 | end 303 | 304 | def get_file_content(file_name_param) 305 | file_name = config[file_name_param] 306 | return if file_name.nil? 307 | 308 | file_name = File.expand_path(file_name) 309 | File.open(file_name, 'r').read 310 | end 311 | end 312 | end 313 | end 314 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_server_delete.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_helper' 5 | require 'chef/knife/oci_common_options' 6 | 7 | class Chef 8 | class Knife 9 | # Server Delete Command: Delete an OCI instance. 10 | class OciServerDelete < Knife 11 | banner 'knife oci server delete (options)' 12 | 13 | include OciHelper 14 | include OciCommonOptions 15 | 16 | # max interval for polling the server state 17 | MAX_INTERVAL_SECONDS = 3 18 | 19 | deps do 20 | require 'oci' 21 | require 'chef/knife/bootstrap' 22 | end 23 | 24 | option :instance_id, 25 | long: '--instance-id INSTANCE', 26 | description: 'The OCID of the instance to be deleted. (required)' 27 | 28 | option :wait, 29 | long: '--wait SECONDS', 30 | description: 'Wait for the instance to be terminated. 0=infinite' 31 | 32 | option :purge, 33 | long: '--purge', 34 | description: 'Remove the corresponding node and client from the Chef Server. The instance display name will be used as the node name, unless --node-name is specified.' 35 | 36 | option :chef_node_name, 37 | short: '-N NAME', 38 | long: '--node-name NAME', 39 | description: 'The name of the Chef node to be removed when using the --purge option. If not specified, the instance display name will be used.' 40 | 41 | def run 42 | $stdout.sync = true 43 | validate_required_params(%i[instance_id], config) 44 | wait_for = validate_wait 45 | if config[:chef_node_name] && !config[:purge] 46 | error_and_exit('--node-name requires --purge argument') 47 | end 48 | 49 | response = check_can_access_instance(config[:instance_id]) 50 | 51 | ui.msg "Instance name: #{response.data.display_name}" 52 | deletion_prompt = 'Delete server? (y/n)' 53 | chef_node = nil 54 | if config[:purge] 55 | deletion_prompt = 'Delete server and chef node? (y/n)' 56 | node_name = response.data.display_name 57 | node_name = config[:chef_node_name] if config[:chef_node_name] 58 | chef_node = get_chef_node(node_name) 59 | ui.msg "Chef node name: #{chef_node.name}" 60 | end 61 | confirm_deletion(deletion_prompt) 62 | 63 | terminate_instance(config[:instance_id]) 64 | delete_chef_node(chef_node) if config[:purge] 65 | delete_chef_client(node_name) if config[:purge] 66 | 67 | wait_for_instance_terminated(config[:instance_id], wait_for) if wait_for 68 | end 69 | 70 | def terminate_instance(instance_id) 71 | compute_client.terminate_instance(instance_id) 72 | 73 | ui.msg "Initiated delete of instance #{instance_id}" 74 | end 75 | 76 | def get_chef_node(node_name) 77 | node = Chef::Node.load(node_name) 78 | node 79 | end 80 | 81 | def delete_chef_node(node) 82 | node.destroy 83 | ui.msg "Deleted Chef node '#{node.name}'" 84 | end 85 | 86 | def delete_chef_client(client_name) 87 | object = Chef::ApiClient.load(client_name) 88 | return unless object && !object.validator 89 | object.destroy 90 | ui.msg "Deleted Chef client '#{client_name}'" 91 | end 92 | 93 | def wait_for_instance_terminated(instance_id, wait_for) 94 | print ui.color('Waiting for instance to terminate...', :magenta) 95 | begin 96 | begin 97 | compute_client.get_instance(instance_id).wait_until(:lifecycle_state, 98 | OCI::Core::Models::Instance::LIFECYCLE_STATE_TERMINATED, 99 | get_wait_options(wait_for)) do 100 | show_progress 101 | end 102 | ensure 103 | end_progress_indicator 104 | end 105 | rescue OCI::Waiter::Errors::MaximumWaitTimeExceededError 106 | error_and_exit 'Timeout exceeded while waiting for instance to terminate' 107 | rescue OCI::Errors::ServiceError => service_error 108 | raise unless service_error.service_code == 'NotAuthorizedOrNotFound' 109 | # we'll soak this exception since the terminate may have completed before we started waiting for it. 110 | ui.warn 'Instance not authorized or not found' 111 | end 112 | end 113 | 114 | def validate_wait 115 | wait_for = nil 116 | if config[:wait] 117 | wait_for = Integer(config[:wait]) 118 | error_and_exit 'Wait value must be 0 or greater' if wait_for < 0 119 | end 120 | wait_for 121 | end 122 | 123 | def get_wait_options(wait_for) 124 | opts = { 125 | max_interval_seconds: MAX_INTERVAL_SECONDS 126 | } 127 | opts[:max_wait_seconds] = wait_for if wait_for > 0 128 | opts 129 | end 130 | 131 | def confirm_deletion(prompt) 132 | if confirm(prompt) 133 | # we have user's confirmation, so avoid any further confirmation prompts from Chef 134 | config[:yes] = true 135 | return 136 | end 137 | error_and_exit 'Server delete canceled.' 138 | end 139 | 140 | def show_progress 141 | print ui.color('.', :magenta) 142 | $stdout.flush 143 | end 144 | 145 | def end_progress_indicator 146 | print ui.color("done\n", :magenta) 147 | end 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_server_list.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_common_options' 5 | require 'chef/knife/oci_helper' 6 | 7 | class Chef 8 | class Knife 9 | # List OCI instances. Note that this lists all instances in a 10 | # compartment, not just those that are set up as Chef nodes. 11 | class OciServerList < Knife 12 | banner 'knife oci server list (options)' 13 | 14 | include OciHelper 15 | include OciCommonOptions 16 | 17 | deps do 18 | require 'oci' 19 | end 20 | 21 | option :limit, 22 | long: '--limit LIMIT', 23 | description: 'The maximum number of items to return.' 24 | 25 | def run 26 | options = {} 27 | options[:limit] = config[:limit] if config[:limit] 28 | 29 | columns = ['Display Name', 'State', 'ID'] 30 | 31 | list_for_display = config[:format] == 'summary' ? bold(columns) : [] 32 | list_data, last_response = get_display_results(options) do |client_options| 33 | response = compute_client.list_instances(compartment_id, client_options) 34 | 35 | items = response_to_list(response) do |item| 36 | [item.display_name, 37 | item.lifecycle_state, 38 | item.id] 39 | end 40 | [response, items] 41 | end 42 | list_for_display += list_data 43 | 44 | display_list_from_array(list_for_display, columns.length) 45 | warn_if_page_is_truncated(last_response) 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_server_show.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_common_options' 5 | require 'chef/knife/oci_helper' 6 | require 'chef/knife/oci_helper_show' 7 | 8 | class Chef 9 | class Knife 10 | # List details of a particular OCI instance. 11 | class OciServerShow < Knife 12 | banner 'knife oci server show (options)' 13 | 14 | include OciHelper 15 | include OciHelperShow 16 | include OciCommonOptions 17 | 18 | deps do 19 | require 'oci' 20 | end 21 | 22 | option :instance_id, 23 | long: '--instance_id INSTANCE', 24 | description: 'The OCID of the server to display. (required)' 25 | 26 | def run 27 | validate_required_params(%i[instance_id], config) 28 | vnic_array = [] 29 | server = check_can_access_instance(config[:instance_id]) 30 | error_and_exit 'Unable to retrieve instance' unless server.data 31 | vnics = compute_client.list_vnic_attachments(compartment_id, instance_id: config[:instance_id]) 32 | vnics.data && vnics.data.each do |vnic| 33 | next unless vnic.lifecycle_state == 'ATTACHED' 34 | begin 35 | vnic_info = network_client.get_vnic(vnic.vnic_id, {}) 36 | rescue OCI::Errors::ServiceError => service_error 37 | raise unless service_error.service_code == 'NotAuthorizedOrNotFound' 38 | else 39 | add_vnic_details(vnic_info.data) 40 | if vnic_info.data.is_primary == true 41 | vnic_array.unshift(vnic_info.data) # make primary interface first in the array 42 | else 43 | vnic_array.push(vnic_info.data) 44 | end 45 | end 46 | end 47 | add_server_details(server.data, vnic_array[0] ? vnic_array[0].vcn_id : nil) 48 | 49 | display_server_info(config, server.data, vnic_array) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_shape_list.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_common_options' 5 | require 'chef/knife/oci_helper' 6 | 7 | class Chef 8 | class Knife 9 | # List available shapes 10 | class OciShapeList < Knife 11 | banner 'knife oci shape list (options)' 12 | 13 | include OciHelper 14 | include OciCommonOptions 15 | 16 | deps do 17 | require 'oci' 18 | end 19 | 20 | option :availability_domain, 21 | long: '--availability-domain AD', 22 | description: 'The Availability Domain of the instance.' 23 | 24 | option :image_id, 25 | long: '--image-id IMAGE', 26 | description: 'The OCID of the image used to boot the instance.' 27 | 28 | option :limit, 29 | long: '--limit LIMIT', 30 | description: 'The maximum number of items to return.' 31 | 32 | def run 33 | options = {} 34 | options[:availability_domain] = config[:availability_domain] if config[:availability_domain] 35 | options[:image_id] = config[:image_id] if config[:image_id] 36 | options[:limit] = config[:limit] if config[:limit] 37 | 38 | columns = [] 39 | 40 | list_for_display, last_response = get_display_results(options) do |client_options| 41 | response = compute_client.list_shapes(compartment_id, client_options) 42 | 43 | items = response_to_list(response) do |item| 44 | [item.shape] 45 | end 46 | [response, items] 47 | end 48 | 49 | list_for_display.uniq! 50 | display_list_from_array(list_for_display, columns.length) 51 | warn_if_page_is_truncated(last_response) 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_subnet_list.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_common_options' 5 | require 'chef/knife/oci_helper' 6 | 7 | class Chef 8 | class Knife 9 | # List OCI subnets in a VCN. 10 | class OciSubnetList < Knife 11 | banner 'knife oci subnet list (options)' 12 | 13 | include OciHelper 14 | include OciCommonOptions 15 | 16 | deps do 17 | require 'oci' 18 | end 19 | 20 | option :limit, 21 | long: '--limit LIMIT', 22 | description: 'The maximum number of items to return.' 23 | 24 | option :vcn_id, 25 | long: '--vcn-id VCN', 26 | description: 'The VCN ID to list subnets for. (required)' 27 | 28 | def run 29 | validate_required_params(%i[vcn_id], config) 30 | options = {} 31 | options[:limit] = config[:limit] if config[:limit] 32 | 33 | columns = ['Display Name', 'ID', 'CIDR Block', 'Availability Domain', 'State'] 34 | 35 | list_for_display = config[:format] == 'summary' ? bold(columns) : [] 36 | list_data, last_response = get_display_results(options) do |client_options| 37 | response = network_client.list_subnets(compartment_id, config[:vcn_id], client_options) 38 | 39 | items = response_to_list(response) do |item| 40 | [item.display_name, item.id, item.cidr_block, item.availability_domain, item.lifecycle_state] 41 | end 42 | [response, items] 43 | end 44 | list_for_display += list_data 45 | 46 | display_list_from_array(list_for_display, columns.length) 47 | warn_if_page_is_truncated(last_response) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/chef/knife/oci_vcn_list.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'chef/knife' 4 | require 'chef/knife/oci_common_options' 5 | require 'chef/knife/oci_helper' 6 | 7 | class Chef 8 | class Knife 9 | # List OCI VCNs. Note that this lists all VCNs in a compartment, not just those that are set up as Chef nodes. 10 | class OciVcnList < Knife 11 | banner 'knife oci vcn list (options)' 12 | 13 | include OciHelper 14 | include OciCommonOptions 15 | 16 | deps do 17 | require 'oci' 18 | end 19 | 20 | option :limit, 21 | long: '--limit LIMIT', 22 | description: 'The maximum number of items to return.' 23 | 24 | def run 25 | options = {} 26 | options[:limit] = config[:limit] if config[:limit] 27 | 28 | columns = ['Display Name', 'ID', 'CIDR Block', 'State'] 29 | 30 | list_for_display = config[:format] == 'summary' ? bold(columns) : [] 31 | list_data, last_response = get_display_results(options) do |client_options| 32 | response = network_client.list_vcns(compartment_id, client_options) 33 | 34 | items = response_to_list(response) do |item| 35 | [item.display_name, item.id, item.cidr_block, item.lifecycle_state] 36 | end 37 | [response, items] 38 | end 39 | list_for_display += list_data 40 | 41 | display_list_from_array(list_for_display, columns.length) 42 | warn_if_page_is_truncated(last_response) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/knife-oci/version.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | module Knife 4 | module OCI 5 | VERSION = '2.0.2'.freeze 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/integration/list_commands_integ_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'oci' 5 | 6 | LIST_OUTPUT_DIRECTORY = 'test_output/list_commands/'.freeze 7 | 8 | def run_command(command, param_hash, file) 9 | params = param_hash.map { |k, v| "#{k} #{v}" }.join(' ') 10 | shell = write_command_to_file("#{command} #{params}", file, LIST_OUTPUT_DIRECTORY) 11 | shell 12 | end 13 | 14 | def validate_output(shell, params) 15 | expect(shell.stdout).to include(params['--availability-domain']) 16 | expect(shell.stdout).to include(params['--image-id']) 17 | expect(shell.stdout).to include('is now running') 18 | expect(shell.stdout).to include('Public IP Address:') 19 | expect(shell.stdout).to include('Chef Client finished') 20 | end 21 | 22 | describe 'list commands' do 23 | let(:params_only_oci_config) do 24 | { 25 | '--oci-config-file' => config_file_path, 26 | '--oci-profile' => profile, 27 | '--config' => KNIFE_CONFIG_FILE 28 | } 29 | end 30 | 31 | let(:params_only_knife_config) do 32 | { 33 | '--config' => KNIFE_CONFIG_FILE 34 | } 35 | end 36 | 37 | let(:params_with_compartment) do 38 | { 39 | '--oci-config-file' => config_file_path, 40 | '--oci-profile' => profile, 41 | '--compartment-id' => compartment_id 42 | } 43 | end 44 | 45 | let(:params_help) do 46 | { 47 | '--help' => true 48 | } 49 | end 50 | 51 | it 'can list availability domains' do 52 | shell = run_command('ad list', params_only_oci_config, 'ad_list') 53 | expect(shell.stdout).to include('AD-2') 54 | end 55 | 56 | it 'can point to oci config and compartment from knife config' do 57 | shell = run_command('image list', params_only_knife_config, 'image_list_with_knife_config') 58 | expect(shell.stdout).to include('Display Name') 59 | expect(shell.stdout).to include('ocid1.image') 60 | end 61 | 62 | it 'can list images' do 63 | shell = run_command('image list', params_with_compartment, 'image_list') 64 | expect(shell.stdout).to include('Display Name') 65 | expect(shell.stdout).to include('ocid1.image') 66 | expect(shell.stderr).not_to include('This list has been truncated.') 67 | end 68 | 69 | it 'can list images with limit' do 70 | params_with_compartment['--limit'] = '1' 71 | shell = run_command('image list', params_with_compartment, 'image_list_with_limit') 72 | expect(shell.stdout).to include('Display Name') 73 | expect(shell.stdout).to include('ocid1.image') 74 | expect(shell.stderr).to include('This list has been truncated.') 75 | end 76 | 77 | it 'can list images with alternate format' do 78 | params_with_compartment['--format'] = 'text' 79 | shell = run_command('image list', params_with_compartment, 'image_list_with_text_format') 80 | expect(shell.stdout).not_to include('Display Name') 81 | expect(shell.stdout).to include('createImageAllowed:') 82 | expect(shell.stderr).not_to include('This list has been truncated.') 83 | end 84 | 85 | it 'can list instances' do 86 | shell = run_command('server list', params_with_compartment, 'server_list') 87 | expect(shell.stdout).to include('Display Name') 88 | expect(shell.stdout).to include('State') 89 | expect(shell.stderr).not_to include('This list has been truncated.') 90 | end 91 | 92 | it 'can list compartments' do 93 | shell = run_command('compartment list', params_only_oci_config, 'compartment_list') 94 | expect(shell.stdout).to include('Display Name') 95 | expect(shell.stdout).to include('ID') 96 | expect(shell.stderr).not_to include('This list has been truncated.') 97 | end 98 | 99 | it 'compartment list help omits compartment-id param' do 100 | shell = run_command('compartment list', params_help, 'compartment_list_help') 101 | expect(shell.stdout).not_to include('compartment-id') 102 | end 103 | 104 | it 'server list help includes compartment-id param' do 105 | shell = run_command('server list', params_help, 'server_list_help') 106 | expect(shell.stdout).to include('compartment-id') 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /spec/integration/server_create_integ_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'oci' 5 | 6 | SERVER_CREATE_OUTPUT_DIRECTORY = 'test_output/server_create/'.freeze 7 | 8 | def should_mock_response 9 | ENV['MOCK_OCI'] && ENV['MOCK_OCI'] != 'false' 10 | end 11 | 12 | def run_server_create(param_hash, file) 13 | puts "Running integ test #{file}..." 14 | if should_mock_response 15 | puts '++++++ Response is being mocked. To stop mocking, unset MOCK_OCI. ++++++' 16 | shell = double(stdout: File.open(SERVER_CREATE_OUTPUT_DIRECTORY + file + '.txt', 'rb').read) 17 | else 18 | params = param_hash.map { |k, v| "#{k} #{v}" }.join(' ') 19 | shell = write_command_to_file("server create #{params}", file, SERVER_CREATE_OUTPUT_DIRECTORY) 20 | end 21 | @latest_output = shell.stdout 22 | puts "Warning: command output is empty. Check #{SERVER_CREATE_OUTPUT_DIRECTORY + file + '.txt'} for error messages" if @latest_output.to_s.empty? 23 | shell 24 | end 25 | 26 | def run_server_delete(param_hash, file) 27 | puts "Running integ test #{file}..." 28 | if should_mock_response 29 | puts '++++++ Response is being mocked. To stop mocking, unset MOCK_OCI. ++++++' 30 | shell = double(stdout: File.open(SERVER_CREATE_OUTPUT_DIRECTORY + file + '.txt', 'rb').read) 31 | else 32 | params = param_hash.map { |k, v| "#{k} #{v}" }.join(' ') 33 | shell = write_command_to_file("server delete #{params}", file, SERVER_CREATE_OUTPUT_DIRECTORY) 34 | end 35 | @latest_output = shell.stdout 36 | puts "Warning: command output is empty. Check #{SERVER_CREATE_OUTPUT_DIRECTORY + file + '.txt'} for error messages" if @latest_output.to_s.empty? 37 | shell 38 | end 39 | 40 | def validate_output(shell, params) 41 | expect(shell.stdout).to include(params['--availability-domain']) 42 | expect(shell.stdout).to include(params['--image-id']) 43 | expect(shell.stdout).to include('is now running') 44 | expect(shell.stdout).to include('Public IP Address:') 45 | expect(shell.stdout).to include('Chef Client finished') 46 | end 47 | 48 | def validate_delete_output(shell, chef_node_name) 49 | expect(shell.stdout).to include("Deleted Chef node '#{chef_node_name}'") 50 | end 51 | 52 | def delete_and_purge 53 | return if @latest_output.nil? 54 | # delete the instance using default chef node name 55 | output = @latest_output 56 | @latest_output = nil 57 | match = output.match("Instance ID:\s(.*)") 58 | return unless match && match.length > 1 59 | instance_id = match[1] 60 | chef_node_name = output.match("Bootstrapping with node name '(.+)'")[1] 61 | puts "Clean Up: Terminating instance #{instance_id}." 62 | yield instance_id, chef_node_name 63 | end 64 | 65 | describe 'server create command' do 66 | let(:min_params) do 67 | { 68 | '--availability-domain' => availability_domain, 69 | '--compartment-id' => compartment_id, 70 | '--subnet-id' => subnet_id, 71 | '--shape' => shape, 72 | '--image-id' => 'Must be set for each run.', 73 | '--oci-config-file' => config_file_path, 74 | '--oci-profile' => profile, 75 | '--ssh-authorized-keys-file' => public_ssh_key_file, 76 | '--identity-file' => private_key_file, 77 | '--ssh-user' => 'opc', 78 | '--yes' => true 79 | } 80 | end 81 | 82 | let(:extra_params) do 83 | { 84 | '--display-name' => 'knife_integ_instance', 85 | '--hostname-label' => 'myintegtesthostname', 86 | '--metadata' => '\'{"key1":"value1", "key2":"value2"}\'', 87 | '--user-data-file' => 'spec/resources/example_user_data.txt' 88 | } 89 | end 90 | 91 | let(:min_delete_params) do 92 | { 93 | '--compartment-id' => compartment_id, 94 | '--oci-config-file' => config_file_path, 95 | '--oci-profile' => profile, 96 | '--yes' => true 97 | } 98 | end 99 | 100 | it 'can create an Oracle Linux instance with min params' do 101 | params = min_params 102 | params['--image-id'] = oracle_linux_image_id 103 | puts params.inspect 104 | shell = run_server_create(params, 'test_oracle_linux_min_params') 105 | validate_output(shell, params) 106 | 107 | delete_and_purge do |instance_id, chef_node_name| 108 | # delete the instance using default chef node name 109 | params = min_delete_params 110 | params['--instance-id'] = instance_id 111 | params['--purge'] = true 112 | shell = run_server_delete(params, 'test_oracle_linux_delete_with_purge') 113 | validate_delete_output(shell, chef_node_name) 114 | end 115 | end 116 | 117 | it 'can create an Oracle Linux instance with all params' do 118 | params = min_params.merge(extra_params) 119 | params['--image-id'] = oracle_linux_image_id 120 | shell = run_server_create(params, 'test_oracle_linux_all_params') 121 | validate_output(shell, params) 122 | expect(shell.stdout).to include(params['--display-name']) 123 | delete_and_purge do |instance_id| 124 | # delete the instance using default chef node name 125 | params = min_delete_params 126 | params['--instance-id'] = instance_id 127 | params['--purge'] = true 128 | shell = run_server_delete(params, 'test_oracle_linux_delete_with_non_default_displayname') 129 | validate_delete_output(shell, extra_params['--display-name']) 130 | end 131 | end 132 | 133 | it 'can create an Ubuntu instance' do 134 | params = min_params 135 | params['--image-id'] = ubuntu_image_id 136 | params['--ssh-user'] = 'ubuntu' 137 | shell = run_server_create(params, 'test_ubuntu') 138 | validate_output(shell, params) 139 | delete_and_purge do |instance_id, chef_node_name| 140 | # delete the instance using specified chef node name 141 | params = min_delete_params 142 | params['--instance-id'] = instance_id 143 | params['--purge'] = true 144 | params['--node-name'] = chef_node_name 145 | shell = run_server_delete(params, 'test_ubuntu_delete') 146 | validate_delete_output(shell, chef_node_name) 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /spec/resources/config_for_unit_tests: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | user=ocid1.user.oc1..aaaaaaaa4fzscrlfqhwccvbipzy7oeaumb3pr447o6htcj2cfw7bmls6m5ea 3 | fingerprint=8a:05:94:0e:49:68:7e:a0:c4:ba:07:94:77:06:2b:a2 4 | key_file=spec/resources/dummy_ssh_key 5 | tenancy=ocidv1:tenancy:oc1:phx:1460406592660:aaaaaaaab4faofrfkxecohhj0123456789 6 | region=us-phoenix-1 7 | 8 | [SECOND_PROFILE] 9 | tenancy=ocidv1:tenancy:oc1:phx:1460406592660:aaaaaaaab4faofrfkxecohhj9876543210 -------------------------------------------------------------------------------- /spec/resources/dummy_ssh_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAunYiwBN9fZXFIpZyWeUPl76Di4GL+6ax8+hmDssB/hZnxvU5 3 | 3y4bFup+/rdvuQvjdHl+dKARLu/y+kmqIl/jdbp7Rl/p87THSf+FCSDXug6KbvMF 4 | R37fc3RzVvdngXBoaoBw7OF3Nb6ge41zEbx+TzDxTWC1IF4hDRt2gTLrLZCFSsXM 5 | ditcaarL1ZC5M9/on5WY/2epBGi9QqcvQQEatDFr1vPqmCRO3dC57lya7IsDW8k9 6 | VtbehoeLGYt6nud4AE5IzrPgiWgr4chcF5jVJaiEoqEpVzuT0LBEGH9j1xcxdmxC 7 | iBqc8CrhtTdYlx2ROlqbaZS19Hvnsi9p08pSMQIDAQABAoIBAQCSmeVyjSBiSAoJ 8 | yq23nhAnZ4O3gLBVuFc7hOIRCW3UnzsSw6WvomlsGAynKgRuQjmgTxGskllQRRoN 9 | zTM1+Zw16NuZljm0AdOai4rGhYr3Xw1adDoXX1pCRTGWE8jOjVmbOscOh6qZl/pH 10 | igwJyuCoVLuz8bAW9csYqhn/NNCRLjJ+5JtlrONWfRQswXSsRW97MSaX6pBZgnPl 11 | 6Q0u5W5clc3xtU1X9O//eX3wrKTWh7IoAe2ioIENvRhU3+2z5fPLvu3Tm6Othqjt 12 | Qw2hWqSKGlVJJ7h7wezObvY3L5OflKscBzTuQfTlcXYJPrGmkq59/1Q04jhOzV0Y 13 | Hgjwf2QRAoGBAOah/hZ0Z+SW4oFCrcL8OpbQx2FxXysxLSjIR3y9JkmfCoyObVml 14 | cm8BLZ/OZQU5AMxGHj29mr1FUe9810TnJhHkVRfNMf6azlhLwmGWlxStXhZIhmHA 15 | FuRxM4QNT90l+lo5c6rtQ745snvAjCeSLGmMwb8C2RKbyL+ZpGxK63NVAoGBAM74 16 | ZWhIKZnHlPNtGs7XGMxbZPw0G1Hmec99Q/5pHMtHlDYTkAufjKhKK4xsjhiPSKaG 17 | PllgnaTJ104HHPAl4kKxJaUIPK7eCjs5eXs8FzNMO5bC7hoPsPx1eRmpHPDX1Xpk 18 | hZoRGX+oac3X/P1VRHARQGUaJ6hAA55GEQizX1ttAoGALoiKwq8T8zw6UUhJ1Oid 19 | TaasQ83jHMzcRrk6K0HiwsHzF+74wzitgRYkxXSYT1hz/8M6AM0LtpqQ8Jgouv9E 20 | YCHIxXeiWbOUZJ5MhvIiY4qjTC2v53Mha1Any8H/1if+fkvPObKLk4sCpxNvc+B7 21 | U4SR5t9FOwrxky8FzM/E980CgYB+bUt8rQAEeN7q583FZa03P+jrCmVZ3SCxd0Ju 22 | iQTWw82sTrzh7L8+GbiKWdFe/T+SwVQawqLpg4Yqmru5klqpBI8LRYHUC7xSOySS 23 | +7zsT/fiewZ5Eva48IVeAGi2UhGMZxUZbLKXwWeIHxTiJxDcKB2e5KAu7ZOIPiKv 24 | nupz7QKBgQCO7v9pVUID0erP51uMIomAwy6s4aeJHU7bLiZPkiA08nOd494SMrFo 25 | rIAzH71jLyW2i0DWIrmJY6rEaE59cdKOVGJ8NRv15paw2bEea5tMDGOTclVZfHBQ 26 | 53FxeD3DKdzyqPh8hhkQMJEU0Jh1S245dv2sdhq6sHAL7Ot8jUIO8Q== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /spec/resources/dummy_ssh_key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6diLAE319lcUilnJZ5Q+XvoOLgYv7prHz6GYOywH+FmfG9TnfLhsW6n7+t2+5C+N0eX50oBEu7/L6SaoiX+N1untGX+nztMdJ/4UJINe6Dopu8wVHft9zdHNW92eBcGhqgHDs4Xc1vqB7jXMRvH5PMPFNYLUgXiENG3aBMustkIVKxcx2K1xpqsvVkLkz3+iflZj/Z6kEaL1Cpy9BARq0MWvW8+qYJE7d0LnuXJrsiwNbyT1W1t6Gh4sZi3qe53gATkjOs+CJaCvhyFwXmNUlqISioSlXO5PQsEQYf2PXFzF2bEKIGpzwKuG1N1iXHZE6WptplLX0e+eyL2nTylIx dummy_ssh_key 2 | -------------------------------------------------------------------------------- /spec/resources/example_user_data.txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "This was created by CloudInit user data." >> user_data_example_output.txt -------------------------------------------------------------------------------- /spec/resources/knife.rb: -------------------------------------------------------------------------------- 1 | # This is a example knife.rb with fake data. 2 | # See https://docs.getchef.com/config_rb_knife.html for more information on knife configuration options 3 | 4 | log_level :info 5 | log_location STDOUT 6 | 7 | chef_server_url 'https://111.111.111.111/organizations/myinc' 8 | 9 | knife[:oci_config_file] = ENV['KNIFE_OCI_CONFIG_FILE'] 10 | knife[:oci_profile] = ENV['KNIFE_OCI_PROFILE'] 11 | knife[:compartment_id] = ENV['KNIFE_OCI_COMPARTMENT'] 12 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require 'simplecov' 4 | SimpleCov.start do 5 | add_filter '/chef-repo/' 6 | end 7 | 8 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 9 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 10 | 11 | require 'chef' 12 | require 'chef/knife' 13 | require 'rspec' 14 | require 'mixlib/shellout' 15 | require 'chef/mixin/shell_out' 16 | 17 | KNIFE_CONFIG_FILE = 'spec/resources/knife.rb'.freeze 18 | 19 | # Dummy public SSH key - not used with any actual instances. 20 | DUMMY_PUBLIC_KEY_FILE = 'spec/resources/dummy_ssh_key.pub'.freeze 21 | 22 | # Dummy private SSH key - not used with any actual instances. 23 | DUMMY_PRIVATE_KEY_FILE = 'spec/resources/dummy_ssh_key'.freeze 24 | 25 | # Config file used for unit test - the info it contains will not work 26 | # with a real tenancy. 27 | DUMMY_CONFIG_FILE = 'spec/resources/config_for_unit_tests'.freeze 28 | 29 | def compartment_id 30 | # Example value: ocid1.compartment.oc1..aaaaaaaa7x9zzwkqlcyupl6msnblrhffavz6bu6phk7q265kfsil3ileabcq 31 | ENV['KNIFE_OCI_COMPARTMENT'] 32 | end 33 | 34 | def availability_domain 35 | # Example value: IxGV:US-ASHBURN-AD-2 36 | ENV['KNIFE_OCI_AD'] 37 | end 38 | 39 | def config_file_path 40 | # Example value: ~/.oci/config 41 | ENV['KNIFE_OCI_CONFIG_FILE'] 42 | end 43 | 44 | def profile 45 | # Example value: DEFAULT 46 | ENV['KNIFE_OCI_PROFILE'] 47 | end 48 | 49 | def subnet_id 50 | # Example value: ocid1.subnet.oc1.iad.aaaaaaaacuqa4rii7bwqyanrarfmqgsz6qptljsvqijcpcspfaty4lab8nza 51 | ENV['KNIFE_OCI_SUBNET'] 52 | end 53 | 54 | def shape 55 | # Example value: VM.Standard1.1 56 | ENV['KNIFE_OCI_SHAPE'] 57 | end 58 | 59 | def oracle_linux_image_id 60 | # Example value: ocid1.image.oc1.iad.aaaaaaaah2d5y4jlyi6q5mus4ihabunzdzuiwmuc3pequv27jfkc5eb4ylcq 61 | ENV['KNIFE_OCI_ORACLE_LINUX_ID'] 62 | end 63 | 64 | def ubuntu_image_id 65 | # Example value: ocid1.image.oc1.iad.aaaaaaaa25xqs7rqfkf7ukgwnpzvyhbg3qd4rplu7yl5tpnmdkzzeudr3s2a 66 | ENV['KNIFE_OCI_UBUNTU_ID'] 67 | end 68 | 69 | def public_ssh_key_file 70 | # Example value: ~/.keys/instance_keys.pub 71 | ENV['KNIFE_OCI_PUBLIC_SSH_KEY_FILE'] 72 | end 73 | 74 | def private_key_file 75 | # Example value: ~/.keys/instance_keys 76 | ENV['KNIFE_OCI_PRIVATE_KEY_FILE'] 77 | end 78 | 79 | RSpec.configure do |rspec| 80 | # Calls to exit(1) produce a SystemExit, which will cause rspec to stop executing any remaining tests and report success. 81 | # This will convert SystemExit to a RuntimeError such that tests will fail in that case. 82 | rspec.around(:example) do |ex| 83 | begin 84 | ex.run 85 | rescue SystemExit 86 | raise 'Unexpected SystemExit called.' 87 | end 88 | end 89 | end 90 | 91 | def write_command_to_file(subcommand, file, directory) 92 | command = "knife oci #{subcommand}" 93 | shell = Mixlib::ShellOut.new(command) 94 | shell.run_command 95 | 96 | output = command 97 | output = output + "\n\n" + shell.stdout unless shell.stdout.empty? 98 | output = output + "\n\n+++ STDERR +++\n\n" + shell.stderr unless shell.stderr.empty? 99 | 100 | Dir.mkdir(directory) unless Dir.exist?(directory) 101 | File.open(File.join(directory, file + '.txt'), 'w') { |f| f.write(output) } 102 | shell 103 | end 104 | -------------------------------------------------------------------------------- /spec/unit/ad_list_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'chef/knife/oci_ad_list' 6 | 7 | def run_tests(output_format) 8 | receive_type = output_format == 'summary' ? :list : :output 9 | 10 | it "shows #{output_format} view" do 11 | knife_oci_compartment_list.config = config 12 | knife_oci_compartment_list.config[:format] = output_format 13 | 14 | allow(knife_oci_compartment_list.identity_client).to receive(:list_availability_domains).and_return(response) 15 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 16 | expect(knife_oci_compartment_list.ui).not_to receive(:warn) 17 | 18 | knife_oci_compartment_list.run 19 | end 20 | 21 | it "shows #{output_format} with empty list" do 22 | knife_oci_compartment_list.config = config 23 | knife_oci_compartment_list.config[:format] = output_format 24 | 25 | allow(knife_oci_compartment_list.identity_client).to receive(:list_availability_domains).and_return(empty_response) 26 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 27 | expect(knife_oci_compartment_list.ui).not_to receive(:warn) 28 | 29 | knife_oci_compartment_list.run 30 | end 31 | 32 | it "shows #{output_format} with nil list" do 33 | knife_oci_compartment_list.config = config 34 | knife_oci_compartment_list.config[:format] = output_format 35 | 36 | allow(knife_oci_compartment_list.identity_client).to receive(:list_availability_domains).and_return(nil_response) 37 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 38 | expect(knife_oci_compartment_list.ui).not_to receive(:warn) 39 | 40 | knife_oci_compartment_list.run 41 | end 42 | end 43 | 44 | Chef::Knife::OciAdList.load_deps 45 | 46 | describe Chef::Knife::OciAdList do 47 | let(:knife_oci_compartment_list) { Chef::Knife::OciAdList.new } 48 | 49 | describe 'run ad list' do 50 | let(:config) do 51 | { 52 | compartment_id: 'compartmentA', 53 | oci_config_file: DUMMY_CONFIG_FILE, 54 | format: 'summary' 55 | } 56 | end 57 | 58 | let(:instance1) do 59 | double(name: 'IwGV:US-DUMMY-AD-1', 60 | compartment_id: 'compartmentA', 61 | to_hash: { 'display_name' => 'hashname' }) 62 | end 63 | 64 | let(:instance2) do 65 | double(name: 'IwGV:US-DUMMY-AD-2', 66 | compartment_id: 'compartmentA', 67 | to_hash: { 'display_name' => 'hashname' }) 68 | end 69 | 70 | let(:instance3) do 71 | double(name: 'IwGV:US-DUMMY-AD-3', 72 | compartment_id: 'compartmentA', 73 | to_hash: { 'display_name' => 'hashname' }) 74 | end 75 | 76 | let(:response) do 77 | double(data: [instance1, instance2, instance3], 78 | headers: {}) 79 | end 80 | 81 | let(:empty_response) do 82 | double(data: [], 83 | headers: {}) 84 | end 85 | 86 | let(:nil_response) do 87 | double(data: nil, 88 | headers: {}) 89 | end 90 | 91 | run_tests('summary') 92 | run_tests('text') 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/unit/bmcs_common_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | 6 | describe 'oci common utilities' do 7 | describe 'loading of config values' do 8 | let(:knife_oci_server_list) { Chef::Knife::OciServerList.new } 9 | it 'loads oci config from knife config' do 10 | knife_oci_server_list.config = { oci_config_file: DUMMY_CONFIG_FILE } 11 | expect(knife_oci_server_list.oci_config.tenancy).to eq('ocidv1:tenancy:oc1:phx:1460406592660:aaaaaaaab4faofrfkxecohhj0123456789') 12 | end 13 | 14 | it 'loads oci profile from knife config' do 15 | knife_oci_server_list.config = { 16 | oci_config_file: DUMMY_CONFIG_FILE, 17 | oci_profile: 'SECOND_PROFILE' 18 | } 19 | expect(knife_oci_server_list.oci_config.tenancy).to eq('ocidv1:tenancy:oc1:phx:1460406592660:aaaaaaaab4faofrfkxecohhj9876543210') 20 | end 21 | end 22 | 23 | describe 'config overrides' do 24 | let(:knife_oci_subnet_list) { Chef::Knife::OciSubnetList.new } 25 | 26 | let(:config) do 27 | { 28 | compartment_id: 'compartmentA', 29 | oci_config_file: DUMMY_CONFIG_FILE, 30 | vcn_id: 'ocid1.vcn.oc1..test' 31 | } 32 | end 33 | 34 | let(:empty_response) do 35 | double(data: [], 36 | headers: {}) 37 | end 38 | 39 | it 'uses overridden region id' do 40 | knife_oci_subnet_list.config = config 41 | knife_oci_subnet_list.config[:region] = 'overridden-region' 42 | 43 | allow(knife_oci_subnet_list.network_client).to receive(:list_subnets).and_return(empty_response) 44 | expect(knife_oci_subnet_list.ui).not_to receive(:warn) 45 | expect(knife_oci_subnet_list.ui).to receive(:output) 46 | expect(knife_oci_subnet_list.oci_config.region).to eq('overridden-region') 47 | expect(knife_oci_subnet_list.compute_client.region).to eq('overridden-region') 48 | 49 | knife_oci_subnet_list.run 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/unit/compartment_list_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'chef/knife/oci_compartment_list' 6 | 7 | # rubocop:disable Metrics/AbcSize 8 | def run_tests(output_format) 9 | receive_type = output_format == 'summary' ? :list : :output 10 | 11 | it "compartment shows #{output_format} view" do 12 | knife_oci_compartment_list.config = config 13 | knife_oci_compartment_list.config[:format] = output_format 14 | 15 | allow(knife_oci_compartment_list.identity_client).to receive(:list_compartments).and_return(multi_response, empty_response) 16 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 17 | expect(knife_oci_compartment_list.ui).not_to receive(:warn) 18 | 19 | knife_oci_compartment_list.run 20 | end 21 | 22 | it "use tenancy not compartment id #{output_format}" do 23 | knife_oci_compartment_list.config = config 24 | knife_oci_compartment_list.config[:format] = output_format 25 | 26 | allow(knife_oci_compartment_list.identity_client).to receive(:list_compartments).and_return(multi_response, empty_response) 27 | expect(knife_oci_compartment_list).not_to receive(:compartment_id) 28 | expect(knife_oci_compartment_list.oci_config).to receive(:tenancy).twice 29 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 30 | expect(knife_oci_compartment_list.ui).not_to receive(:warn) 31 | 32 | knife_oci_compartment_list.run 33 | end 34 | 35 | it "compartment shows #{output_format} with nil list" do 36 | knife_oci_compartment_list.config = config 37 | knife_oci_compartment_list.config[:format] = output_format 38 | 39 | allow(knife_oci_compartment_list.identity_client).to receive(:list_compartments).and_return(nil_response) 40 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 41 | expect(knife_oci_compartment_list.ui).not_to receive(:warn) 42 | 43 | knife_oci_compartment_list.run 44 | end 45 | 46 | it "shows response #{output_format} with empty list" do 47 | knife_oci_compartment_list.config = config 48 | knife_oci_compartment_list.config[:format] = output_format 49 | 50 | allow(knife_oci_compartment_list.identity_client).to receive(:list_compartments).and_return(empty_response) 51 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 52 | expect(knife_oci_compartment_list.ui).not_to receive(:warn) 53 | 54 | knife_oci_compartment_list.run 55 | end 56 | 57 | it "shows warning #{output_format} when truncated" do 58 | knife_oci_compartment_list.config = config 59 | knife_oci_compartment_list.config[:format] = output_format 60 | knife_oci_compartment_list.config[:limit] = 1 61 | response = multi_response 62 | response.headers['opc-next-page'] = 'page2' 63 | 64 | allow(knife_oci_compartment_list.identity_client).to receive(:list_compartments).and_return(response, empty_response) 65 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 66 | expect(knife_oci_compartment_list.ui).to receive(:warn).with('This list has been truncated. To view more items, increase the limit.') 67 | 68 | knife_oci_compartment_list.run 69 | end 70 | 71 | it "does not show warning #{output_format} when next page is empty" do 72 | knife_oci_compartment_list.config = config 73 | knife_oci_compartment_list.config[:format] = output_format 74 | response = multi_response 75 | response.headers['opc-next-page'] = 'page2' 76 | 77 | allow(knife_oci_compartment_list.identity_client).to receive(:list_compartments).and_return(response, empty_response) 78 | expect(knife_oci_compartment_list.ui).to receive(receive_type) 79 | expect(knife_oci_compartment_list.ui).to_not receive(:warn) 80 | 81 | knife_oci_compartment_list.run 82 | end 83 | end 84 | 85 | Chef::Knife::OciCompartmentList.load_deps 86 | 87 | describe Chef::Knife::OciCompartmentList do 88 | let(:knife_oci_compartment_list) { Chef::Knife::OciCompartmentList.new } 89 | 90 | describe 'list compartment' do 91 | let(:config) do 92 | { 93 | oci_config_file: DUMMY_CONFIG_FILE, 94 | format: 'summary' 95 | } 96 | end 97 | 98 | let(:compartment) do 99 | double(compartmentId: 'ocid1.tenancy.oc1..test', 100 | id: '12345', 101 | name: 'Chef', 102 | description: 'Chef Test Compartment', 103 | lifecycleState: 'ACTIVE', 104 | to_hash: { 'display_name' => 'hash_name' }) 105 | end 106 | 107 | let(:multi_response) do 108 | double(data: [compartment, compartment], 109 | headers: { 'opc-next-page' => 'aaaaaaaaaaaaaaaa' }) 110 | end 111 | 112 | let(:empty_response) do 113 | double(data: [], 114 | headers: {}) 115 | end 116 | 117 | let(:nil_response) do 118 | double(data: nil, 119 | headers: {}) 120 | end 121 | 122 | run_tests('summary') 123 | run_tests('text') 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /spec/unit/help_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | 5 | OUTPUT_DIRECTORY = 'test_output/help/'.freeze 6 | 7 | def write_help(subcommand, file) 8 | shell = write_command_to_file("#{subcommand} --help", file, OUTPUT_DIRECTORY) 9 | expect(shell.stdout).to include('--help') 10 | shell 11 | end 12 | 13 | describe 'show help for each command' do 14 | it 'oci displays a help message' do 15 | write_help('', 'oci') 16 | end 17 | 18 | it 'server create displays a help message' do 19 | write_help('server create', 'server_create') 20 | end 21 | 22 | it 'server delete displays a help message' do 23 | write_help('server delete', 'server_delete') 24 | end 25 | 26 | it 'image list displays a help message' do 27 | write_help('image list', 'image_list') 28 | end 29 | 30 | it 'shape list displays a help message' do 31 | write_help('shape list', 'shape_list') 32 | end 33 | 34 | it 'ad list displays a help message' do 35 | write_help('ad list', 'ad_list') 36 | end 37 | 38 | it 'server list displays a help message' do 39 | write_help('server list', 'server_list') 40 | end 41 | 42 | it 'compartment list displays a help message' do 43 | write_help('compartment list', 'compartment_list') 44 | end 45 | 46 | it 'subnet list displays a help message' do 47 | write_help('subnet list', 'subnet_list') 48 | end 49 | 50 | it 'vcn list displays a help message' do 51 | write_help('vcn list', 'vcn_list') 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/unit/image_list_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'chef/knife/oci_image_list' 6 | 7 | # rubocop:disable Metrics/AbcSize 8 | def run_tests(output_format) 9 | receive_type = output_format == 'summary' ? :list : :output 10 | 11 | it "shows #{output_format} view" do 12 | knife_oci_server_list.config = config 13 | knife_oci_server_list.config[:format] = output_format 14 | 15 | allow(knife_oci_server_list.compute_client).to receive(:list_images).and_return(response) 16 | expect(knife_oci_server_list.ui).to receive(receive_type) 17 | expect(knife_oci_server_list.ui).not_to receive(:warn) 18 | 19 | knife_oci_server_list.run 20 | end 21 | 22 | it "shows #{output_format} with empty list" do 23 | knife_oci_server_list.config = config 24 | knife_oci_server_list.config[:format] = output_format 25 | 26 | allow(knife_oci_server_list.compute_client).to receive(:list_images).and_return(empty_response) 27 | expect(knife_oci_server_list.ui).to receive(receive_type) 28 | expect(knife_oci_server_list.ui).not_to receive(:warn) 29 | 30 | knife_oci_server_list.run 31 | end 32 | 33 | it "shows #{output_format} with nil list" do 34 | knife_oci_server_list.config = config 35 | knife_oci_server_list.config[:format] = output_format 36 | 37 | allow(knife_oci_server_list.compute_client).to receive(:list_images).and_return(nil_response) 38 | expect(knife_oci_server_list.ui).to receive(receive_type) 39 | expect(knife_oci_server_list.ui).not_to receive(:warn) 40 | 41 | knife_oci_server_list.run 42 | end 43 | 44 | it "warns #{output_format} when truncated" do 45 | knife_oci_server_list.config = config 46 | knife_oci_server_list.config[:format] = output_format 47 | knife_oci_server_list.config[:limit] = 1 48 | response.headers['opc-next-page'] = 'page2' 49 | 50 | allow(knife_oci_server_list.compute_client).to receive(:list_images).and_return(response, empty_response) 51 | expect(knife_oci_server_list.ui).to receive(receive_type) 52 | expect(knife_oci_server_list.ui).to receive(:warn).with('This list has been truncated. To view more items, increase the limit.') 53 | 54 | knife_oci_server_list.run 55 | end 56 | end 57 | 58 | Chef::Knife::OciImageList.load_deps 59 | 60 | describe Chef::Knife::OciImageList do 61 | let(:knife_oci_server_list) { Chef::Knife::OciImageList.new } 62 | 63 | describe 'run shape list' do 64 | let(:config) do 65 | { 66 | compartment_id: 'compartmentA', 67 | oci_config_file: DUMMY_CONFIG_FILE, 68 | format: 'summary' 69 | } 70 | end 71 | 72 | let(:image1) do 73 | double(id: 'ocid1.image.oc1.DUMMY.1', 74 | lifecycle_state: 'AVAILABLE', 75 | operating_system: 'Windows', 76 | operating_system_version: 'Server 2012 R2 Standard', 77 | display_name: 'Windows-Server-2012-R2-Standard-Edition-BM-2017.07.25-0', 78 | create_image_allowed: true, 79 | to_hash: { 'display_name' => 'hashname' }) 80 | end 81 | 82 | let(:image2) do 83 | double(id: 'ocid1.image.oc1.DUMMY.2', 84 | lifecycle_state: 'AVAILABLE', 85 | operating_system: 'Oracle Linux', 86 | operating_system_version: '7.3', 87 | display_name: 'Oracle-Linux-7.3-2017.07.17-0', 88 | create_image_allowed: true, 89 | to_hash: { 'display_name' => 'hashname' }) 90 | end 91 | 92 | let(:image3) do 93 | double(id: 'ocid1.image.oc1.DUMMY.3', 94 | lifecycle_state: 'AVAILABLE', 95 | operating_system: 'CentOS', 96 | operating_system_version: '7', 97 | display_name: 'CentOS-7-2017.07.17-0', 98 | create_image_allowed: true, 99 | to_hash: { 'display_name' => 'hashname' }) 100 | end 101 | 102 | let(:image4) do 103 | double(id: 'ocid1.image.oc1.DUMMY.4', 104 | lifecycle_state: 'DISABLED', 105 | operating_system: 'Multics', 106 | operating_system_version: '12.6f', 107 | display_name: 'Multics-12.6f-(Y2K fixes)', 108 | create_image_allowed: false, 109 | to_hash: { 'display_name' => 'hashname' }) 110 | end 111 | 112 | let(:response) do 113 | double(data: [image1, image2, image3, image4], 114 | headers: {}) 115 | end 116 | 117 | let(:empty_response) do 118 | double(data: [], 119 | headers: {}) 120 | end 121 | 122 | let(:nil_response) do 123 | double(data: nil, 124 | headers: {}) 125 | end 126 | 127 | run_tests('summary') 128 | run_tests('text') 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/unit/server_create_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'date' 6 | require 'chef/knife/oci_server_create' 7 | 8 | Chef::Knife::OciServerCreate.load_deps 9 | 10 | # rubocop:disable Metrics/BlockLength 11 | describe Chef::Knife::OciServerCreate do 12 | let(:knife_oci_server_create) { Chef::Knife::OciServerCreate.new } 13 | 14 | describe 'run server create' do 15 | let(:compartment) do 16 | double(:compartment, 17 | description: 'my testing compartment', 18 | id: 'compartmentA', 19 | name: 'compartmentA name', 20 | to_hash: { 'display_name' => 'hashname' }) 21 | end 22 | 23 | let(:image) do 24 | double(:image, 25 | display_name: 'myimage', 26 | id: 'myimage-id', 27 | to_hash: { 'display_name' => 'hashname' }) 28 | end 29 | 30 | let(:instance) do 31 | double(availability_domain: 'ad1', 32 | compartment_id: 'compartmentA', 33 | display_name: 'myname', 34 | id: '12345', 35 | image_id: 'myimage', 36 | lifecycle_state: 'RUNNING', 37 | region: 'phx', 38 | shape: 'round', 39 | subnet_id: 'supersubnet', 40 | time_created: DateTime.new(2017, 7, 16, 12, 13, 14)) 41 | end 42 | 43 | let(:vcn) do 44 | double(:vcn, 45 | compartment_id: 'compartmentA', 46 | display_name: 'myvcn-display-name', 47 | id: 'myvcn-id', 48 | to_hash: { 'display_name' => 'hashname' }) 49 | end 50 | 51 | let(:vnic) do 52 | double(public_ip: '123.456.789.101', 53 | private_ip: '10.0.0.0', 54 | is_primary: true, 55 | subnet_id: 'supersubnet', 56 | hostname_label: 'myhostname') 57 | end 58 | 59 | let(:subnet) do 60 | double(:subnet, 61 | id: 'supersubnet', 62 | display_name: 'compartment A test subnet', 63 | subnet_domain_name: 'mysubnet.myvcn.oraclevcn.com', 64 | vcn_id: 'myvcn') 65 | end 66 | 67 | let(:min_config) do 68 | { 69 | availability_domain: 'ad1', 70 | compartment_id: 'compartmentA', 71 | image_id: 'myimage', 72 | shape: 'round', 73 | subnet_id: 'supersubnet', 74 | ssh_authorized_keys_file: DUMMY_PUBLIC_KEY_FILE, 75 | identity_file: DUMMY_PRIVATE_KEY_FILE, 76 | oci_config_file: DUMMY_CONFIG_FILE 77 | } 78 | end 79 | 80 | it 'should list missing required params' do 81 | expect(knife_oci_server_create.ui).to receive(:error).with('Missing the following required parameters: availability-domain, image-id, shape, subnet-id, identity-file, ssh-authorized-keys-file') 82 | expect { knife_oci_server_create.run }.to raise_error(SystemExit) 83 | end 84 | 85 | it 'runs with minimal parameters' do 86 | knife_oci_server_create.config = min_config 87 | 88 | allow(knife_oci_server_create.compute_client).to receive(:launch_instance).and_return(double(data: instance)) 89 | allow(knife_oci_server_create).to receive(:wait_for_ssh).and_return(true) 90 | allow(knife_oci_server_create).to receive(:wait_for_instance_running).and_return(instance) 91 | allow(knife_oci_server_create).to receive(:get_vnic).and_return(vnic) 92 | allow(knife_oci_server_create).to receive(:wait_to_stabilize) 93 | allow(knife_oci_server_create.network_client).to receive(:get_subnet).and_return(double(data: subnet)) 94 | allow(knife_oci_server_create.identity_client).to receive(:get_compartment).and_return(double(data: compartment)) 95 | allow(knife_oci_server_create.compute_client).to receive(:get_image).and_return(double(data: image)) 96 | allow(knife_oci_server_create.network_client).to receive(:get_vcn).and_return(double(data: vcn)) 97 | expect(knife_oci_server_create).to receive(:bootstrap) 98 | expect(knife_oci_server_create.ui).to receive(:msg).at_least(10).times 99 | expect(knife_oci_server_create).to receive(:get_vnic).with('12345', 'compartmentA') 100 | 101 | knife_oci_server_create.run 102 | end 103 | 104 | it 'should show error when file not found' do 105 | knife_oci_server_create.config[:user_data_file] = 'notarealfile.dat' 106 | expect { knife_oci_server_create.get_file_content(:user_data_file) }.to raise_error(Errno::ENOENT) 107 | end 108 | 109 | it 'should expand file paths' do 110 | knife_oci_server_create.config[:user_data_file] = '~/notarealfile.dat' 111 | expect { knife_oci_server_create.get_file_content(:user_data_file) }.to raise_error do |error| 112 | expect(error.to_s).to include(ENV['USER'] || ' ') 113 | end 114 | end 115 | 116 | it 'should wait user specified durations for ssh and stabilize' do 117 | knife_oci_server_create.config = min_config 118 | knife_oci_server_create.config[:wait_to_stabilize] = 99 119 | knife_oci_server_create.config[:wait_for_ssh_max] = 188 120 | 121 | allow(knife_oci_server_create.compute_client).to receive(:launch_instance).and_return(double(data: instance)) 122 | allow(knife_oci_server_create).to receive(:wait_for_ssh).with(vnic.public_ip, 22, 2, 188).and_return(true) 123 | allow(knife_oci_server_create).to receive(:wait_for_instance_running).and_return(instance) 124 | allow(knife_oci_server_create).to receive(:get_vnic).and_return(vnic) 125 | allow(knife_oci_server_create.network_client).to receive(:get_subnet).and_return(double(data: subnet)) 126 | allow(knife_oci_server_create.identity_client).to receive(:get_compartment).and_return(double(data: compartment)) 127 | allow(knife_oci_server_create.compute_client).to receive(:get_image).and_return(double(data: image)) 128 | allow(knife_oci_server_create.network_client).to receive(:get_vcn).and_return(double(data: vcn)) 129 | expect(Kernel).to receive(:sleep).with(99) 130 | expect(knife_oci_server_create).to receive(:bootstrap) 131 | expect(knife_oci_server_create.ui).to receive(:msg).at_least(10).times 132 | 133 | knife_oci_server_create.run 134 | end 135 | 136 | it 'should wait default durations for ssh and stabilize' do 137 | knife_oci_server_create.config = min_config 138 | 139 | allow(knife_oci_server_create.compute_client).to receive(:launch_instance).and_return(double(data: instance)) 140 | allow(knife_oci_server_create).to receive(:wait_for_ssh).with(vnic.public_ip, 22, 2, 300).and_return(true) 141 | allow(knife_oci_server_create).to receive(:wait_for_instance_running).and_return(instance) 142 | allow(knife_oci_server_create).to receive(:get_vnic).and_return(vnic) 143 | allow(knife_oci_server_create.network_client).to receive(:get_subnet).and_return(double(data: subnet)) 144 | allow(knife_oci_server_create.identity_client).to receive(:get_compartment).and_return(double(data: compartment)) 145 | allow(knife_oci_server_create.compute_client).to receive(:get_image).and_return(double(data: image)) 146 | allow(knife_oci_server_create.network_client).to receive(:get_vcn).and_return(double(data: vcn)) 147 | expect(Kernel).to receive(:sleep).with(40) 148 | expect(knife_oci_server_create).to receive(:bootstrap) 149 | expect(knife_oci_server_create.ui).to receive(:msg).at_least(10).times 150 | 151 | knife_oci_server_create.run 152 | end 153 | end 154 | 155 | describe 'oci_config' do 156 | it 'should load default values' do 157 | expect(OCI::ConfigFileLoader).to receive(:load_config).with(config_file_location: "#{Dir.home}/.oci/config", 158 | profile_name: 'DEFAULT').and_return(OCI::Config.new) 159 | knife_oci_server_create.oci_config 160 | end 161 | 162 | it 'should load values from command line' do 163 | knife_oci_server_create.config[:oci_config_file] = 'myconfig' 164 | knife_oci_server_create.config[:oci_profile] = 'nobody' 165 | expect(OCI::ConfigFileLoader).to receive(:load_config).with(config_file_location: 'myconfig', 166 | profile_name: 'nobody').and_return(OCI::Config.new) 167 | knife_oci_server_create.oci_config 168 | end 169 | 170 | it 'should show error when file not found' do 171 | knife_oci_server_create.config[:oci_config_file] = 'notarealconfigfile' 172 | expect { knife_oci_server_create.oci_config }.to raise_error.with_message(/Config file does not exist/) 173 | end 174 | 175 | it 'should show error when profile not found' do 176 | knife_oci_server_create.config[:oci_config_file] = DUMMY_CONFIG_FILE 177 | knife_oci_server_create.config[:oci_profile] = 'notarealprofile' 178 | expect { knife_oci_server_create.oci_config }.to raise_error.with_message(/Profile not found/) 179 | end 180 | 181 | it 'should add to user agent' do 182 | knife_oci_server_create.config[:oci_config_file] = DUMMY_CONFIG_FILE 183 | expect(knife_oci_server_create.oci_config.additional_user_agent).to eq 'Oracle-ChefKnifeOCI/2.0.2' 184 | end 185 | end 186 | 187 | describe 'wait_for_ssh' do 188 | it 'should return false on timeout' do 189 | allow(knife_oci_server_create).to receive(:can_ssh).and_return(false) 190 | expect(knife_oci_server_create).to receive(:show_progress).at_least(10).times 191 | expect(knife_oci_server_create.ui).to receive(:color).once.ordered.with('Waiting for ssh access...', :magenta) 192 | expect(knife_oci_server_create.ui).to receive(:color).once.ordered.with("done\n", :magenta) 193 | expect(knife_oci_server_create.wait_for_ssh('111.111.111.111', 22, 0.01, 0.5)).to eq(false) 194 | end 195 | 196 | it 'should return immediately on success' do 197 | allow(knife_oci_server_create).to receive(:can_ssh).and_return(true) 198 | expect(knife_oci_server_create).to receive(:show_progress).exactly(0).times 199 | expect(knife_oci_server_create.ui).to receive(:color).once.ordered.with('Waiting for ssh access...', :magenta) 200 | expect(knife_oci_server_create.ui).to receive(:color).once.ordered.with("done\n", :magenta) 201 | expect(knife_oci_server_create.wait_for_ssh('111.111.111.111', 22, 0.01, 0.5)).to eq(true) 202 | end 203 | end 204 | 205 | describe 'merge_metadata' do 206 | it 'should merge metadata from all sources' do 207 | knife_oci_server_create.config[:ssh_authorized_keys_file] = DUMMY_PUBLIC_KEY_FILE 208 | knife_oci_server_create.config[:user_data_file] = 'spec/resources/example_user_data.txt' 209 | knife_oci_server_create.config[:metadata] = '{"key1":"value1", "key2":"value2"}' 210 | metadata = knife_oci_server_create.merge_metadata 211 | expect(metadata.keys.length).to eq 4 212 | expect(metadata).to have_key('ssh_authorized_keys') 213 | expect(metadata['ssh_authorized_keys'].length).to be > 0 214 | expect(metadata['user_data']).to eq 'IyEvYmluL2Jhc2gKCmVjaG8gIlRoaXMgd2FzIGNyZWF0ZWQgYnkgQ2xvdWRJbml0IHVzZXIgZGF0YS4iID4+IHVzZXJfZGF0YV9leGFtcGxlX291dHB1dC50eHQ=' 215 | expect(metadata['key1']).to eq 'value1' 216 | expect(metadata['key2']).to eq 'value2' 217 | end 218 | 219 | it 'should merge metadata from ssh keys only' do 220 | knife_oci_server_create.config[:ssh_authorized_keys_file] = DUMMY_PUBLIC_KEY_FILE 221 | metadata = knife_oci_server_create.merge_metadata 222 | expect(metadata.keys.length).to eq 1 223 | expect(metadata).to have_key('ssh_authorized_keys') 224 | expect(metadata['ssh_authorized_keys'].length).to be > 0 225 | end 226 | 227 | it 'should merge metadata from metadata param only' do 228 | knife_oci_server_create.config[:metadata] = '{"user_data":"mydata", "ssh_authorized_keys":"mykeys"}' 229 | metadata = knife_oci_server_create.merge_metadata 230 | expect(metadata.keys.length).to eq 2 231 | expect(metadata).to have_key('ssh_authorized_keys') 232 | expect(metadata['ssh_authorized_keys']).to eq 'mykeys' 233 | expect(metadata['user_data']).to eq 'mydata' 234 | end 235 | 236 | it 'should show error if metadata is not in json format' do 237 | knife_oci_server_create.config[:metadata] = '{"key1":"value1", "key2":"value2" invalid}' 238 | expect(knife_oci_server_create.ui).to receive(:error).with('Metadata value must be in JSON format. Example: \'{"key1":"value1", "key2":"value2"}\'') 239 | expect { knife_oci_server_create.merge_metadata }.to raise_error(SystemExit) 240 | end 241 | 242 | it 'should show error when user_data given twice' do 243 | knife_oci_server_create.config[:metadata] = '{"user_data":"mydata", "ssh_authorized_keys":"mykeys"}' 244 | knife_oci_server_create.config[:user_data_file] = 'spec/resources/example_user_data.txt' 245 | expect(knife_oci_server_create.ui).to receive(:error) 246 | expect { knife_oci_server_create.merge_metadata }.to raise_error(SystemExit) 247 | end 248 | 249 | it 'should show error when ssh_authorized_keys given twice' do 250 | knife_oci_server_create.config[:metadata] = '{"user_data":"mydata", "ssh_authorized_keys":"mykeys"}' 251 | knife_oci_server_create.config[:ssh_authorized_keys_file] = DUMMY_PUBLIC_KEY_FILE 252 | expect(knife_oci_server_create.ui).to receive(:error) 253 | expect { knife_oci_server_create.merge_metadata }.to raise_error(SystemExit) 254 | end 255 | end 256 | end 257 | -------------------------------------------------------------------------------- /spec/unit/server_delete_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'chef/knife/oci_server_delete' 6 | 7 | Chef::Knife::OciServerDelete.load_deps 8 | 9 | describe Chef::Knife::OciServerDelete do 10 | let(:knife_oci_server_delete) { Chef::Knife::OciServerDelete.new } 11 | 12 | describe 'run server delete' do 13 | let(:config) do 14 | { 15 | compartment_id: 'compartmentA', 16 | oci_config_file: DUMMY_CONFIG_FILE, 17 | instance_id: 'ocid1.instance.oc1.test', 18 | yes: true 19 | } 20 | end 21 | 22 | let(:nil_response) do 23 | nil 24 | end 25 | 26 | let(:get_server_ok_response) do 27 | double(data: instance, 28 | headers: {}) 29 | end 30 | 31 | let(:get_server_terminated_response) do 32 | double(data: terminated_instance, 33 | headers: {}) 34 | end 35 | 36 | let(:instance) do 37 | double(availability_domain: 'ad1', 38 | compartment_id: 'compartmentA', 39 | display_name: 'myname', 40 | id: 'ocid1.instance.oc1.test', 41 | image_id: 'myimage', 42 | region: 'phx', 43 | shape: 'round', 44 | subnet_id: 'supersubnet', 45 | lifecycle_state: 'RUNNING') 46 | end 47 | 48 | let(:terminated_instance) do 49 | double(availability_domain: 'ad1', 50 | compartment_id: 'compartmentA', 51 | display_name: 'myname', 52 | id: 'ocid1.instance.oc1.test', 53 | image_id: 'myimage', 54 | region: 'phx', 55 | shape: 'round', 56 | subnet_id: 'supersubnet', 57 | lifecycle_state: 'TERMINATED') 58 | end 59 | 60 | let(:chef_client) do 61 | double(name: 'myname') 62 | end 63 | 64 | let(:chef_node) do 65 | double(name: 'myname') 66 | end 67 | 68 | let(:chef_node_newname) do 69 | double(name: 'newname') 70 | end 71 | 72 | it 'should list missing required params' do 73 | expect(knife_oci_server_delete.ui).to receive(:error).with('Missing the following required parameters: instance-id') 74 | expect { knife_oci_server_delete.run }.to raise_error(SystemExit) 75 | end 76 | 77 | it 'should fail if node-name but not purge provided' do 78 | knife_oci_server_delete.config = config 79 | knife_oci_server_delete.config[:chef_node_name] = 'test' 80 | knife_oci_server_delete.config.delete(:purge) 81 | 82 | expect(knife_oci_server_delete.ui).to receive(:error).with('--node-name requires --purge argument') 83 | expect { knife_oci_server_delete.run }.to raise_error(SystemExit) 84 | end 85 | 86 | it 'should delete remote instance and default named chef node' do 87 | knife_oci_server_delete.config = config 88 | knife_oci_server_delete.config[:purge] = true 89 | 90 | allow(knife_oci_server_delete.compute_client).to receive(:terminate_instance).and_return(nil_response) 91 | allow(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_ok_response) 92 | allow(Chef::Node).to receive(:load).with('myname').and_return(chef_node) 93 | allow(Chef::ApiClient).to receive(:load).with('myname').and_return(chef_client) 94 | expect(chef_node).to receive(:destroy) 95 | expect(chef_client).to receive(:destroy) 96 | expect(chef_client).to receive(:validator) 97 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Instance name: myname') 98 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Chef node name: myname') 99 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Initiated delete of instance ocid1.instance.oc1.test') 100 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with("Deleted Chef node 'myname'") 101 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with("Deleted Chef client 'myname'") 102 | expect(knife_oci_server_delete.ui).not_to receive(:warn) 103 | 104 | knife_oci_server_delete.run 105 | end 106 | 107 | it 'should delete remote instance and specifically named chef node' do 108 | knife_oci_server_delete.config = config 109 | knife_oci_server_delete.config[:purge] = true 110 | knife_oci_server_delete.config[:chef_node_name] = 'newname' 111 | 112 | allow(knife_oci_server_delete.compute_client).to receive(:terminate_instance).and_return(nil_response) 113 | allow(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_ok_response) 114 | allow(Chef::Node).to receive(:load).with('newname').and_return(chef_node_newname) 115 | allow(Chef::ApiClient).to receive(:load).with('newname').and_return(chef_client) 116 | expect(chef_node_newname).to receive(:destroy) 117 | expect(chef_client).to receive(:destroy) 118 | expect(chef_client).to receive(:validator) 119 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Instance name: myname') 120 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Chef node name: newname') 121 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Initiated delete of instance ocid1.instance.oc1.test') 122 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with("Deleted Chef node 'newname'") 123 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with("Deleted Chef client 'newname'") 124 | expect(knife_oci_server_delete.ui).not_to receive(:warn) 125 | 126 | knife_oci_server_delete.run 127 | end 128 | 129 | it 'should delete remote instance' do 130 | knife_oci_server_delete.config = config 131 | 132 | allow(knife_oci_server_delete.compute_client).to receive(:terminate_instance).and_return(nil_response) 133 | allow(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_ok_response) 134 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Instance name: myname') 135 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Initiated delete of instance ocid1.instance.oc1.test') 136 | expect(knife_oci_server_delete.ui).not_to receive(:warn) 137 | 138 | knife_oci_server_delete.run 139 | end 140 | 141 | it 'wait options should reflect wait argument' do 142 | knife_oci_server_delete.config = config 143 | 144 | knife_oci_server_delete.config[:wait] = '-1' 145 | expect(knife_oci_server_delete.ui).to receive(:error).with('Wait value must be 0 or greater') 146 | expect { knife_oci_server_delete.run }.to raise_error(SystemExit) 147 | 148 | expect(knife_oci_server_delete.get_wait_options(0)).to eq(max_interval_seconds: 3) 149 | expect(knife_oci_server_delete.get_wait_options(1)).to eq(max_interval_seconds: 3, max_wait_seconds: 1) 150 | end 151 | 152 | it 'should fail if instance not accessible' do 153 | knife_oci_server_delete.config = config 154 | 155 | allow(knife_oci_server_delete.compute_client).to receive(:get_instance).and_raise(OCI::Errors::ServiceError.new(200, 'NotAuthorizedOrNotFound', 'test_request_id', 'Not authorized')) 156 | expect(knife_oci_server_delete.compute_client).to_not receive(:terminate_instance) 157 | expect(knife_oci_server_delete.ui).to receive(:error).with('Instance not authorized or not found') 158 | expect { knife_oci_server_delete.run }.to raise_error(SystemExit) 159 | end 160 | 161 | it 'should fail if instance already terminated' do 162 | knife_oci_server_delete.config = config 163 | 164 | allow(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_terminated_response) 165 | expect(knife_oci_server_delete.compute_client).to_not receive(:terminate_instance) 166 | expect(knife_oci_server_delete.ui).to receive(:error).with('Instance is already in terminated state') 167 | expect { knife_oci_server_delete.run }.to raise_error(SystemExit) 168 | end 169 | 170 | it 'should wait for instance to disappear' do 171 | knife_oci_server_delete.config = config 172 | knife_oci_server_delete.config[:wait] = '60' 173 | 174 | allow(knife_oci_server_delete.compute_client).to receive(:terminate_instance).and_return(nil_response) 175 | allow(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_ok_response) 176 | expect(knife_oci_server_delete).to receive(:wait_for_instance_terminated) 177 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Instance name: myname') 178 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Initiated delete of instance ocid1.instance.oc1.test') 179 | 180 | knife_oci_server_delete.run 181 | end 182 | 183 | it 'should not wait for instance to disappear if no wait' do 184 | knife_oci_server_delete.config = config 185 | knife_oci_server_delete.config[:wait] = nil 186 | 187 | allow(knife_oci_server_delete.compute_client).to receive(:terminate_instance).and_return(nil_response) 188 | allow(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_ok_response) 189 | expect(knife_oci_server_delete).to_not receive(:wait_for_instance_terminated) 190 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Instance name: myname') 191 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Initiated delete of instance ocid1.instance.oc1.test') 192 | 193 | knife_oci_server_delete.run 194 | end 195 | 196 | it 'negative delete confirmation should exit' do 197 | knife_oci_server_delete.config = config 198 | knife_oci_server_delete.config.delete(:yes) 199 | 200 | allow(knife_oci_server_delete.ui).to receive(:ask).and_return('n').exactly(1).times 201 | expect(knife_oci_server_delete.compute_client).to_not receive(:terminate_instance) 202 | expect(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_ok_response) 203 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Instance name: myname') 204 | expect(knife_oci_server_delete.ui).to receive(:ask).with('Delete server? (y/n)') 205 | expect(knife_oci_server_delete.ui).to receive(:error).with('Server delete canceled.') 206 | expect { knife_oci_server_delete.run }.to raise_error(SystemExit) 207 | end 208 | 209 | it 'positive delete confirmation should proceed' do 210 | knife_oci_server_delete.config = config 211 | knife_oci_server_delete.config.delete(:yes) 212 | 213 | allow(knife_oci_server_delete.ui).to receive(:ask).and_return('Y').exactly(1).times 214 | allow(knife_oci_server_delete.compute_client).to receive(:terminate_instance).and_return(nil_response) 215 | allow(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_ok_response) 216 | expect(knife_oci_server_delete.ui).to receive(:ask).with('Delete server? (y/n)') 217 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Instance name: myname') 218 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Initiated delete of instance ocid1.instance.oc1.test') 219 | expect(knife_oci_server_delete.ui).not_to receive(:warn) 220 | 221 | knife_oci_server_delete.run 222 | end 223 | 224 | it 'delete confirmation with invalid response should retry limited times' do 225 | knife_oci_server_delete.config = config 226 | knife_oci_server_delete.config.delete(:yes) 227 | 228 | allow(knife_oci_server_delete.ui).to receive(:ask).and_return('zn').exactly(3).times 229 | expect(knife_oci_server_delete.compute_client).to_not receive(:terminate_instance) 230 | expect(knife_oci_server_delete.compute_client).to receive(:get_instance).and_return(get_server_ok_response) 231 | expect(knife_oci_server_delete.ui).to receive(:msg).once.ordered.with('Instance name: myname') 232 | expect(knife_oci_server_delete.ui).to receive(:ask).with('Delete server? (y/n)') 233 | expect(knife_oci_server_delete.ui).to receive(:warn).with('Valid responses are ["yes", "no", "y", "n"]').exactly(3).times 234 | expect(knife_oci_server_delete.ui).to receive(:error).with('Server delete canceled.') 235 | expect { knife_oci_server_delete.run }.to raise_error(SystemExit) 236 | end 237 | end 238 | end 239 | -------------------------------------------------------------------------------- /spec/unit/server_list_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'chef/knife/oci_server_list' 6 | 7 | # rubocop:disable Metrics/AbcSize 8 | def run_tests(output_format) 9 | receive_type = output_format == 'summary' ? :list : :output 10 | 11 | it "shows #{output_format} view" do 12 | knife_oci_server_list.config = config 13 | knife_oci_server_list.config[:format] = output_format 14 | 15 | allow(knife_oci_server_list.compute_client).to receive(:list_instances).and_return(response) 16 | expect(knife_oci_server_list.ui).to receive(receive_type) 17 | expect(knife_oci_server_list.ui).not_to receive(:warn) 18 | 19 | knife_oci_server_list.run 20 | end 21 | 22 | it "shows #{output_format} with empty list" do 23 | knife_oci_server_list.config = config 24 | knife_oci_server_list.config[:format] = output_format 25 | 26 | allow(knife_oci_server_list.compute_client).to receive(:list_instances).and_return(empty_response) 27 | expect(knife_oci_server_list.ui).to receive(receive_type) 28 | expect(knife_oci_server_list.ui).not_to receive(:warn) 29 | 30 | knife_oci_server_list.run 31 | end 32 | 33 | it "shows #{output_format} with nil list" do 34 | knife_oci_server_list.config = config 35 | knife_oci_server_list.config[:format] = output_format 36 | 37 | allow(knife_oci_server_list.compute_client).to receive(:list_instances).and_return(nil_response) 38 | expect(knife_oci_server_list.ui).to receive(receive_type) 39 | expect(knife_oci_server_list.ui).not_to receive(:warn) 40 | 41 | knife_oci_server_list.run 42 | end 43 | 44 | it "warns #{output_format} when truncated" do 45 | knife_oci_server_list.config = config 46 | knife_oci_server_list.config[:format] = output_format 47 | knife_oci_server_list.config[:limit] = 1 48 | response.headers['opc-next-page'] = 'page2' 49 | 50 | allow(knife_oci_server_list.compute_client).to receive(:list_instances).and_return(response, empty_response) 51 | expect(knife_oci_server_list.ui).to receive(receive_type) 52 | expect(knife_oci_server_list.ui).to receive(:warn).with('This list has been truncated. To view more items, increase the limit.') 53 | 54 | knife_oci_server_list.run 55 | end 56 | end 57 | 58 | Chef::Knife::OciServerList.load_deps 59 | 60 | describe Chef::Knife::OciServerList do 61 | let(:knife_oci_server_list) { Chef::Knife::OciServerList.new } 62 | 63 | describe 'run server list' do 64 | let(:config) do 65 | { 66 | compartment_id: 'compartmentA', 67 | oci_config_file: DUMMY_CONFIG_FILE, 68 | format: 'summary' 69 | } 70 | end 71 | 72 | let(:instance) do 73 | double(display_name: 'myname', 74 | id: '12345', 75 | lifecycle_state: 'RUNNING', 76 | to_hash: { 'display_name' => 'hashname' }) 77 | end 78 | 79 | let(:response) do 80 | double(data: [instance], 81 | headers: {}) 82 | end 83 | 84 | let(:empty_response) do 85 | double(data: [], 86 | headers: {}) 87 | end 88 | 89 | let(:nil_response) do 90 | double(data: nil, 91 | headers: {}) 92 | end 93 | 94 | run_tests('summary') 95 | run_tests('text') 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/unit/server_show_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'date' 6 | require 'chef/knife/oci_server_show' 7 | 8 | # rubocop:disable Metrics/AbcSize 9 | def run_tests(output_format) 10 | receive_type = output_format == 'text' ? :msg : :output 11 | 12 | it "shows #{output_format} view" do 13 | knife_oci_server_show.config = config 14 | knife_oci_server_show.config[:format] = output_format 15 | 16 | allow(knife_oci_server_show.compute_client).to receive(:get_instance).and_return(response) 17 | allow(knife_oci_server_show.compute_client).to receive(:list_vnic_attachments).and_return(vnics) 18 | allow(knife_oci_server_show.network_client).to receive(:get_vnic).and_return(double(data: vnic_info1), double(data: vnic_info2)) 19 | allow(knife_oci_server_show.network_client).to receive(:get_subnet).and_return(double(data: subnet1)) 20 | allow(knife_oci_server_show.identity_client).to receive(:get_compartment).and_return(double(data: compartmentA)) 21 | allow(knife_oci_server_show.compute_client).to receive(:get_image).and_return(double(data: image1)) 22 | allow(knife_oci_server_show.network_client).to receive(:get_vcn).and_return(double(data: vcn1)) 23 | expect(knife_oci_server_show.ui).to receive(receive_type).at_least(7).times 24 | expect(knife_oci_server_show.ui).not_to receive(:warn) 25 | 26 | knife_oci_server_show.run 27 | end 28 | 29 | it "shows #{output_format} view with failed get_vnic request" do 30 | knife_oci_server_show.config = config 31 | knife_oci_server_show.config[:format] = output_format 32 | 33 | allow(knife_oci_server_show.compute_client).to receive(:get_instance).and_return(response) 34 | allow(knife_oci_server_show.compute_client).to receive(:list_vnic_attachments).and_return(vnics) 35 | allow(knife_oci_server_show.network_client).to receive(:get_vnic).and_raise( 36 | OCI::Errors::ServiceError.new(404, 'NotAuthorizedOrNotFound', 'test_request_id', 'Not authorized') 37 | ) 38 | allow(knife_oci_server_show.network_client).to receive(:get_subnet).and_return(double(data: subnet1)) 39 | allow(knife_oci_server_show.identity_client).to receive(:get_compartment).and_return(double(data: compartmentA)) 40 | allow(knife_oci_server_show.compute_client).to receive(:get_image).and_return(double(data: image1)) 41 | allow(knife_oci_server_show.network_client).to receive(:get_vcn).and_return(double(data: vcn1)) 42 | expect(knife_oci_server_show.ui).to receive(receive_type).at_least(7).times 43 | expect(knife_oci_server_show.ui).not_to receive(:warn) 44 | 45 | knife_oci_server_show.run 46 | end 47 | 48 | it "shows #{output_format} view with many failed requests" do 49 | knife_oci_server_show.config = config 50 | knife_oci_server_show.config[:format] = output_format 51 | 52 | allow(knife_oci_server_show.compute_client).to receive(:get_instance).and_return(response) 53 | allow(knife_oci_server_show.compute_client).to receive(:list_vnic_attachments).and_return(vnics) 54 | allow(knife_oci_server_show.network_client).to receive(:get_vnic).and_return(double(data: vnic_info1)) 55 | allow(knife_oci_server_show.network_client).to receive(:get_subnet).and_raise( 56 | OCI::Errors::ServiceError.new(404, 'NotAuthorizedOrNotFound', 'test_request_id', 'Not authorized') 57 | ) 58 | allow(knife_oci_server_show.identity_client).to receive(:get_compartment).and_raise( 59 | OCI::Errors::ServiceError.new(404, 'NotAuthorizedOrNotFound', 'test_request_id', 'Not authorized') 60 | ) 61 | allow(knife_oci_server_show.compute_client).to receive(:get_image).and_raise( 62 | OCI::Errors::ServiceError.new(404, 'NotAuthorizedOrNotFound', 'test_request_id', 'Not authorized') 63 | ) 64 | allow(knife_oci_server_show.network_client).to receive(:get_vcn).and_raise( 65 | OCI::Errors::ServiceError.new(404, 'NotAuthorizedOrNotFound', 'test_request_id', 'Not authorized') 66 | ) 67 | expect(knife_oci_server_show.ui).to receive(receive_type).at_least(7).times 68 | expect(knife_oci_server_show.ui).not_to receive(:warn) 69 | 70 | knife_oci_server_show.run 71 | end 72 | 73 | it "shows #{output_format} with nil list" do 74 | knife_oci_server_show.config = config 75 | knife_oci_server_show.config[:format] = output_format 76 | 77 | allow(knife_oci_server_show.compute_client).to receive(:get_instance).and_return(nil_response) 78 | expect(knife_oci_server_show.ui).not_to receive(:warn) 79 | 80 | expect { knife_oci_server_show.run }.to raise_error(SystemExit) 81 | end 82 | end 83 | 84 | Chef::Knife::OciServerShow.load_deps 85 | 86 | describe Chef::Knife::OciServerShow do 87 | let(:knife_oci_server_show) { Chef::Knife::OciServerShow.new } 88 | 89 | describe 'run server show' do 90 | let(:compartmentA) do 91 | double(:compartmentA, 92 | description: 'myname', 93 | id: 'compartmentA', 94 | name: 'compartmentA name', 95 | to_hash: { 'display_name' => 'hashname' }) 96 | end 97 | 98 | let(:config) do 99 | { 100 | instance_id: 'ocid1.instance.oc1.test_server_show', 101 | compartment_id: 'compartmentA', 102 | oci_config_file: DUMMY_CONFIG_FILE, 103 | format: 'summary' 104 | } 105 | end 106 | 107 | let(:image1) do 108 | double(:image1, 109 | display_name: 'myimage-name-1', 110 | id: 'myimage-1', 111 | to_hash: { 'display_name' => 'hashname' }) 112 | end 113 | 114 | let(:instance) do 115 | double(:instance, 116 | availability_domain: 'ad1', 117 | compartment_id: 'compartmentA', 118 | display_name: 'myname', 119 | id: '12345', 120 | image_id: 'myimage-1', 121 | lifecycle_state: 'RUNNING', 122 | region: 'regionA', 123 | shape: 'oblate-spheroid', 124 | time_created: DateTime.new(2017, 7, 16, 12, 13, 14), 125 | to_hash: { 'display_name' => 'hashname' }) 126 | end 127 | 128 | let(:response) do 129 | double(data: instance, 130 | headers: {}) 131 | end 132 | 133 | let(:nil_response) do 134 | double(data: nil, 135 | headers: {}) 136 | end 137 | 138 | let(:subnet1) do 139 | double(:subnet1, 140 | id: 'mysubnet-1', 141 | display_name: 'compartmentA test subnet mysubnet-1', 142 | subnet_domain_name: 'mysubnet-1.mycvn1.oraclevcn.com', 143 | vcn_id: 'myvcn1', 144 | to_hash: { 'display_name' => 'hashname' }) 145 | end 146 | 147 | let(:vcn1) do 148 | double(:vcn1, 149 | compartment_id: 'compartmentA', 150 | display_name: 'myvcn-1 display name', 151 | id: 'myvcn1', 152 | to_hash: { 'display_name' => 'hashname' }) 153 | end 154 | 155 | let(:vnic1) do 156 | double(:vnic1, 157 | instance_id: '12345', 158 | vnic_id: '34567', 159 | lifecycle_state: 'ATTACHED') 160 | end 161 | 162 | let(:vnic2) do 163 | double(:vnic2, 164 | instance_id: '12346', 165 | vnic_id: '34568', 166 | lifecycle_state: 'ATTACHED') 167 | end 168 | 169 | let(:vnics) do 170 | double(data: [vnic1, vnic2], 171 | headers: {}) 172 | end 173 | 174 | let(:vnic_info1) do 175 | double(:vnic_info1, 176 | hostname_label: 'mylabel', 177 | id: '34567', 178 | is_primary: true, 179 | private_ip: '10.0.0.1', 180 | public_ip: '129.213.29.14', 181 | subnet_id: 'mysubnet-1', 182 | lifecycle_state: 'ATTACHED') 183 | end 184 | 185 | let(:vnic_info2) do 186 | double(:vnic_info2, 187 | hostname_label: 'mylabel-vnic-2', 188 | id: '34568', 189 | is_primary: false, 190 | private_ip: '10.0.0.2', 191 | public_ip: '129.213.29.15', 192 | subnet_id: 'mysubnet-1', 193 | lifecycle_state: 'ATTACHED') 194 | end 195 | 196 | run_tests('text') 197 | end 198 | end 199 | -------------------------------------------------------------------------------- /spec/unit/shape_list_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'chef/knife/oci_shape_list' 6 | 7 | # rubocop:disable Metrics/AbcSize 8 | def run_tests(output_format) 9 | receive_type = output_format == 'summary' ? :list : :output 10 | 11 | it "shows #{output_format} view" do 12 | knife_oci_server_show.config = config 13 | knife_oci_server_show.config[:format] = output_format 14 | 15 | allow(knife_oci_server_show.compute_client).to receive(:list_shapes).and_return(response) 16 | expect(knife_oci_server_show.ui).to receive(receive_type) 17 | expect(knife_oci_server_show.ui).not_to receive(:warn) 18 | 19 | knife_oci_server_show.run 20 | end 21 | 22 | it "shows #{output_format} with empty list" do 23 | knife_oci_server_show.config = config 24 | knife_oci_server_show.config[:format] = output_format 25 | 26 | allow(knife_oci_server_show.compute_client).to receive(:list_shapes).and_return(empty_response) 27 | expect(knife_oci_server_show.ui).to receive(receive_type) 28 | expect(knife_oci_server_show.ui).not_to receive(:warn) 29 | 30 | knife_oci_server_show.run 31 | end 32 | 33 | it "shows #{output_format} with nil list" do 34 | knife_oci_server_show.config = config 35 | knife_oci_server_show.config[:format] = output_format 36 | 37 | allow(knife_oci_server_show.compute_client).to receive(:list_shapes).and_return(nil_response) 38 | expect(knife_oci_server_show.ui).to receive(receive_type) 39 | expect(knife_oci_server_show.ui).not_to receive(:warn) 40 | 41 | knife_oci_server_show.run 42 | end 43 | 44 | it "warns #{output_format} when truncated" do 45 | knife_oci_server_show.config = config 46 | knife_oci_server_show.config[:format] = output_format 47 | knife_oci_server_show.config[:limit] = 1 48 | response.headers['opc-next-page'] = 'page2' 49 | 50 | allow(knife_oci_server_show.compute_client).to receive(:list_shapes).and_return(response, empty_response) 51 | expect(knife_oci_server_show.ui).to receive(receive_type) 52 | expect(knife_oci_server_show.ui).to receive(:warn).with('This list has been truncated. To view more items, increase the limit.') 53 | 54 | knife_oci_server_show.run 55 | end 56 | end 57 | 58 | Chef::Knife::OciShapeList.load_deps 59 | 60 | describe Chef::Knife::OciShapeList do 61 | let(:knife_oci_server_show) { Chef::Knife::OciShapeList.new } 62 | 63 | describe 'run shape list' do 64 | let(:config) do 65 | { 66 | compartment_id: 'compartmentA', 67 | oci_config_file: DUMMY_CONFIG_FILE, 68 | format: 'summary' 69 | } 70 | end 71 | 72 | let(:shape1) do 73 | double(shape: 'BM.Standard1.36', 74 | to_hash: { 'display_name' => 'hashname' }) 75 | end 76 | 77 | let(:shape2) do 78 | double(shape: 'VM.Standard1.2', 79 | to_hash: { 'display_name' => 'hashname' }) 80 | end 81 | 82 | let(:shape3) do 83 | double(shape: 'VM.Standard1.8', 84 | to_hash: { 'display_name' => 'hashname' }) 85 | end 86 | 87 | let(:shape4) do 88 | double(shape: 'VM.DenseIO1.4', 89 | to_hash: { 'display_name' => 'hashname' }) 90 | end 91 | 92 | let(:response) do 93 | double(data: [shape1, shape2, shape3, shape4], 94 | headers: {}) 95 | end 96 | 97 | let(:empty_response) do 98 | double(data: [], 99 | headers: {}) 100 | end 101 | 102 | let(:nil_response) do 103 | double(data: nil, 104 | headers: {}) 105 | end 106 | 107 | run_tests('summary') 108 | run_tests('text') 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /spec/unit/subnet_list_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'chef/knife/oci_subnet_list' 6 | 7 | # rubocop:disable Metrics/AbcSize 8 | def run_tests(output_format) 9 | receive_type = output_format == 'summary' ? :list : :output 10 | 11 | it 'missing parameters should exit with error' do 12 | expect(knife_oci_subnet_list.ui).to receive(:error).with('Missing the following required parameters: vcn-id') 13 | expect { knife_oci_subnet_list.run }.to raise_error(SystemExit) 14 | end 15 | 16 | it "shows #{output_format} view" do 17 | knife_oci_subnet_list.config = config 18 | knife_oci_subnet_list.config[:format] = output_format 19 | 20 | allow(knife_oci_subnet_list.network_client).to receive(:list_subnets).and_return(multi_response) 21 | expect(knife_oci_subnet_list.ui).to receive(receive_type) 22 | expect(knife_oci_subnet_list.ui).not_to receive(:warn) 23 | 24 | knife_oci_subnet_list.run 25 | end 26 | 27 | it "shows #{output_format} with nil list" do 28 | knife_oci_subnet_list.config = config 29 | knife_oci_subnet_list.config[:format] = output_format 30 | 31 | allow(knife_oci_subnet_list.network_client).to receive(:list_subnets).and_return(nil_response) 32 | expect(knife_oci_subnet_list.ui).to receive(receive_type) 33 | expect(knife_oci_subnet_list.ui).not_to receive(:warn) 34 | 35 | knife_oci_subnet_list.run 36 | end 37 | 38 | it "shows #{output_format} with empty list" do 39 | knife_oci_subnet_list.config = config 40 | knife_oci_subnet_list.config[:format] = output_format 41 | 42 | allow(knife_oci_subnet_list.network_client).to receive(:list_subnets).and_return(empty_response) 43 | expect(knife_oci_subnet_list.ui).to receive(receive_type) 44 | expect(knife_oci_subnet_list.ui).not_to receive(:warn) 45 | 46 | knife_oci_subnet_list.run 47 | end 48 | 49 | it "shows warning #{output_format} when truncated" do 50 | knife_oci_subnet_list.config = config 51 | knife_oci_subnet_list.config[:format] = output_format 52 | knife_oci_subnet_list.config[:limit] = 1 53 | response = multi_response 54 | response.headers['opc-next-page'] = 'page2' 55 | 56 | allow(knife_oci_subnet_list.network_client).to receive(:list_subnets).and_return(response, empty_response) 57 | expect(knife_oci_subnet_list.ui).to receive(receive_type) 58 | expect(knife_oci_subnet_list.ui).to receive(:warn).with('This list has been truncated. To view more items, increase the limit.') 59 | 60 | knife_oci_subnet_list.run 61 | end 62 | end 63 | 64 | Chef::Knife::OciSubnetList.load_deps 65 | 66 | describe Chef::Knife::OciSubnetList do 67 | let(:knife_oci_subnet_list) { Chef::Knife::OciSubnetList.new } 68 | 69 | describe 'list subnet' do 70 | let(:config) do 71 | { 72 | compartment_id: 'compartmentA', 73 | oci_config_file: DUMMY_CONFIG_FILE, 74 | vcn_id: 'ocid1.vcn.oc1..test' 75 | } 76 | end 77 | 78 | let(:subnet) do 79 | double(compartmentId: 'ocid1.tenancy.oc1..test', 80 | id: 'ocid1.subnet.oc1..test', 81 | display_name: 'subnet_1', 82 | cidr_block: '10.0.0.0/24', 83 | availability_domain: 'test-ad', 84 | lifecycle_state: 'ACTIVE', 85 | to_hash: { 'display_name' => 'hash_value' }) 86 | end 87 | 88 | let(:multi_response) do 89 | double(data: [subnet, subnet], 90 | headers: {}) 91 | end 92 | 93 | let(:empty_response) do 94 | double(data: [], 95 | headers: {}) 96 | end 97 | 98 | let(:nil_response) do 99 | double(data: nil, 100 | headers: {}) 101 | end 102 | 103 | run_tests('summary') 104 | run_tests('text') 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/unit/vcn_list_spec.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. 2 | 3 | require './spec/spec_helper' 4 | require 'json' 5 | require 'chef/knife/oci_vcn_list' 6 | 7 | # rubocop:disable Metrics/AbcSize 8 | def run_tests(output_format) 9 | receive_type = output_format == 'summary' ? :list : :output 10 | 11 | it "shows #{output_format} view" do 12 | knife_oci_vcn_list.config = config 13 | knife_oci_vcn_list.config[:format] = output_format 14 | 15 | allow(knife_oci_vcn_list.network_client).to receive(:list_vcns).and_return(multi_response) 16 | expect(knife_oci_vcn_list.ui).to receive(receive_type) 17 | expect(knife_oci_vcn_list.ui).not_to receive(:warn) 18 | 19 | knife_oci_vcn_list.run 20 | end 21 | 22 | it "shows #{output_format} with nil list" do 23 | knife_oci_vcn_list.config = config 24 | knife_oci_vcn_list.config[:format] = output_format 25 | 26 | allow(knife_oci_vcn_list.network_client).to receive(:list_vcns).and_return(nil_response) 27 | expect(knife_oci_vcn_list.ui).to receive(receive_type) 28 | expect(knife_oci_vcn_list.ui).not_to receive(:warn) 29 | 30 | knife_oci_vcn_list.run 31 | end 32 | 33 | it "shows #{output_format} with empty list" do 34 | knife_oci_vcn_list.config = config 35 | knife_oci_vcn_list.config[:format] = output_format 36 | 37 | allow(knife_oci_vcn_list.network_client).to receive(:list_vcns).and_return(empty_response) 38 | expect(knife_oci_vcn_list.ui).to receive(receive_type) 39 | expect(knife_oci_vcn_list.ui).not_to receive(:warn) 40 | 41 | knife_oci_vcn_list.run 42 | end 43 | 44 | it "warns #{output_format} when truncated" do 45 | knife_oci_vcn_list.config = config 46 | knife_oci_vcn_list.config[:format] = output_format 47 | knife_oci_vcn_list.config[:limit] = 1 48 | response = multi_response 49 | response.headers['opc-next-page'] = 'page2' 50 | 51 | allow(knife_oci_vcn_list.network_client).to receive(:list_vcns).and_return(response, empty_response) 52 | expect(knife_oci_vcn_list.ui).to receive(receive_type) 53 | expect(knife_oci_vcn_list.ui).to receive(:warn).with('This list has been truncated. To view more items, increase the limit.') 54 | 55 | knife_oci_vcn_list.run 56 | end 57 | end 58 | 59 | Chef::Knife::OciVcnList.load_deps 60 | 61 | describe Chef::Knife::OciVcnList do 62 | let(:knife_oci_vcn_list) { Chef::Knife::OciVcnList.new } 63 | 64 | describe 'list vcn' do 65 | let(:config) do 66 | { 67 | compartment_id: 'compartmentA', 68 | oci_config_file: DUMMY_CONFIG_FILE 69 | } 70 | end 71 | 72 | let(:vcn) do 73 | double(compartmentId: 'ocid1.tenancy.oc1..test', 74 | id: 'ocid1.vcn.oc1..test', 75 | display_name: 'vcn_1', 76 | cidr_block: '10.0.0.0/24', 77 | lifecycle_state: 'ACTIVE', 78 | to_hash: { 'display_name' => 'hash_value' }) 79 | end 80 | 81 | let(:multi_response) do 82 | double(data: [vcn, vcn], 83 | headers: {}) 84 | end 85 | 86 | let(:empty_response) do 87 | double(data: [], 88 | headers: {}) 89 | end 90 | 91 | let(:nil_response) do 92 | double(data: nil, 93 | headers: {}) 94 | end 95 | 96 | run_tests('summary') 97 | run_tests('text') 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /test_output/help/ad_list.txt: -------------------------------------------------------------------------------- 1 | knife oci ad list --help 2 | 3 | knife oci ad list (options) 4 | -s, --server-url URL Chef Server URL 5 | --chef-zero-host HOST Host to start chef-zero on 6 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 7 | -k, --key KEY API Client Key 8 | --[no-]color Use colored output, defaults to enabled 9 | --compartment-id COMPARTMENT The OCID of the compartment. 10 | -c, --config CONFIG The configuration file to use 11 | --config-option OPTION=VALUE Override a single configuration option 12 | --defaults Accept default values for all questions 13 | -d, --disable-editing Do not open EDITOR, just accept the data as is 14 | -e, --editor EDITOR Set the editor to use for interactive commands 15 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 16 | --[no-]fips Enable fips mode 17 | -F, --format FORMAT Which format to use for output 18 | --[no-]listen Whether a local mode (-z) server binds to a port 19 | -z, --local-mode Point knife commands at local repository instead of server 20 | -u, --user USER API Client Username 21 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 22 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 23 | --print-after Show the data after a destructive operation 24 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 25 | -V, --verbose More verbose output. Use twice for max verbosity 26 | -v, --version Show chef version 27 | -y, --yes Say yes to all prompts for confirmation 28 | -h, --help Show this message 29 | -------------------------------------------------------------------------------- /test_output/help/compartment_list.txt: -------------------------------------------------------------------------------- 1 | knife oci compartment list --help 2 | 3 | knife oci compartment list (options) 4 | -s, --server-url URL Chef Server URL 5 | --chef-zero-host HOST Host to start chef-zero on 6 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 7 | -k, --key KEY API Client Key 8 | --[no-]color Use colored output, defaults to enabled 9 | -c, --config CONFIG The configuration file to use 10 | --config-option OPTION=VALUE Override a single configuration option 11 | --defaults Accept default values for all questions 12 | -d, --disable-editing Do not open EDITOR, just accept the data as is 13 | -e, --editor EDITOR Set the editor to use for interactive commands 14 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 15 | --[no-]fips Enable fips mode 16 | -F, --format FORMAT Which format to use for output 17 | --limit LIMIT The maximum number of items to return. 18 | --[no-]listen Whether a local mode (-z) server binds to a port 19 | -z, --local-mode Point knife commands at local repository instead of server 20 | -u, --user USER API Client Username 21 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 22 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 23 | --print-after Show the data after a destructive operation 24 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 25 | -V, --verbose More verbose output. Use twice for max verbosity 26 | -v, --version Show chef version 27 | -y, --yes Say yes to all prompts for confirmation 28 | -h, --help Show this message 29 | -------------------------------------------------------------------------------- /test_output/help/image_list.txt: -------------------------------------------------------------------------------- 1 | knife oci image list --help 2 | 3 | knife oci image list (options) 4 | -s, --server-url URL Chef Server URL 5 | --chef-zero-host HOST Host to start chef-zero on 6 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 7 | -k, --key KEY API Client Key 8 | --[no-]color Use colored output, defaults to enabled 9 | --compartment-id COMPARTMENT The OCID of the compartment. 10 | -c, --config CONFIG The configuration file to use 11 | --config-option OPTION=VALUE Override a single configuration option 12 | --defaults Accept default values for all questions 13 | -d, --disable-editing Do not open EDITOR, just accept the data as is 14 | -e, --editor EDITOR Set the editor to use for interactive commands 15 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 16 | --[no-]fips Enable fips mode 17 | -F, --format FORMAT Which format to use for output 18 | --limit LIMIT The maximum number of items to return. 19 | --[no-]listen Whether a local mode (-z) server binds to a port 20 | -z, --local-mode Point knife commands at local repository instead of server 21 | -u, --user USER API Client Username 22 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 23 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 24 | --print-after Show the data after a destructive operation 25 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 26 | -V, --verbose More verbose output. Use twice for max verbosity 27 | -v, --version Show chef version 28 | -y, --yes Say yes to all prompts for confirmation 29 | -h, --help Show this message 30 | -------------------------------------------------------------------------------- /test_output/help/oci.txt: -------------------------------------------------------------------------------- 1 | knife oci --help 2 | 3 | Available oci subcommands: (for details, knife SUB-COMMAND --help) 4 | 5 | ** OCI COMMANDS ** 6 | knife oci ad list (options) 7 | knife oci compartment list (options) 8 | knife oci image list (options) 9 | knife oci server create (options) 10 | knife oci server delete (options) 11 | knife oci server list (options) 12 | knife oci server show (options) 13 | knife oci shape list (options) 14 | knife oci subnet list (options) 15 | knife oci vcn list (options) 16 | 17 | 18 | 19 | +++ STDERR +++ 20 | 21 | FATAL: Cannot find subcommand for: 'oci --help' 22 | -------------------------------------------------------------------------------- /test_output/help/server_create.txt: -------------------------------------------------------------------------------- 1 | knife oci server create --help 2 | 3 | knife oci server create (options) 4 | --availability-domain AD The Availability Domain of the instance. (required) 5 | -N, --node-name NAME The Chef node name for the new node. If not specified, the instance display name will be used. 6 | -s, --server-url URL Chef Server URL 7 | --chef-zero-host HOST Host to start chef-zero on 8 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 9 | -k, --key KEY API Client Key 10 | --[no-]color Use colored output, defaults to enabled 11 | --compartment-id COMPARTMENT The OCID of the compartment. 12 | -c, --config CONFIG The configuration file to use 13 | --config-option OPTION=VALUE Override a single configuration option 14 | --defaults Accept default values for all questions 15 | -d, --disable-editing Do not open EDITOR, just accept the data as is 16 | --display-name NAME A user-friendly name for the instance. Does not have to be unique, and it's changeable. 17 | -e, --editor EDITOR Set the editor to use for interactive commands 18 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 19 | --[no-]fips Enable fips mode 20 | -F, --format FORMAT Which format to use for output 21 | --hostname-label HOSTNAME The hostname for the VNIC that is created during instance launch. Used for DNS. The value is the hostname portion of the instance's fully qualified domain name (FQDN). Must be unique across all VNICs in the subnet and comply with RFC 952 and RFC 1123. The value cannot be changed, and it can be retrieved from the Vnic object. 22 | -i IDENTITY_FILE, The SSH identity file used for authentication. This must correspond to a public SSH key provided by --ssh-authorized-keys-file. (required) 23 | --identity-file 24 | --image-id IMAGE The OCID of the image used to boot the instance. (required) 25 | --[no-]listen Whether a local mode (-z) server binds to a port 26 | -z, --local-mode Point knife commands at local repository instead of server 27 | --metadata METADATA Custom metadata key/value pairs in JSON format. 28 | -u, --user USER API Client Username 29 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 30 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 31 | --print-after Show the data after a destructive operation 32 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 33 | -r, --run-list RUN_LIST A comma-separated list of roles or recipes. 34 | --shape SHAPE The shape of an instance. The shape determines the number of CPUs, amount of memory, and other resources allocated to the instance. (required) 35 | --ssh-authorized-keys-file FILE 36 | A file containing one or more public SSH keys to be included in the ~/.ssh/authorized_keys file for the default user on the instance. Use a newline character to separate multiple keys. The SSH keys must be in the format necessary for the authorized_keys file. This parameter is a convenience wrapper around the 'ssh_authorized_keys' field of the --metadata parameter. Populating both values in the same call will result in an error. For more info see documentation: https://docs.us-phoenix-1.oraclecloud.com/api/#/en/iaas/20160918/requests/LaunchInstanceDetails. (required) 37 | -P, --ssh-password PASSWORD The SSH password 38 | -x, --ssh-user USERNAME The SSH username. Defaults to opc. 39 | --subnet-id SUBNET The OCID of the subnet. (required) 40 | --user-data-file FILE A file containing data that Cloud-Init can use to run custom scripts or provide custom Cloud-Init configuration. This parameter is a convenience wrapper around the 'user_data' field of the --metadata parameter. Populating both values in the same call will result in an error. For more info see Cloud-Init documentation: https://cloudinit.readthedocs.org/en/latest/topics/format.html. 41 | -V, --verbose More verbose output. Use twice for max verbosity 42 | -v, --version Show chef version 43 | --wait-for-ssh-max SECONDS The maximum time to wait for SSH to become reachable. Default: 300 44 | --wait-to-stabilize SECONDS Duration to pause after SSH becomes reachable. Default: 40 45 | -y, --yes Say yes to all prompts for confirmation 46 | -h, --help Show this message 47 | -------------------------------------------------------------------------------- /test_output/help/server_delete.txt: -------------------------------------------------------------------------------- 1 | knife oci server delete --help 2 | 3 | knife oci server delete (options) 4 | -N, --node-name NAME The name of the Chef node to be removed when using the --purge option. If not specified, the instance display name will be used. 5 | -s, --server-url URL Chef Server URL 6 | --chef-zero-host HOST Host to start chef-zero on 7 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 8 | -k, --key KEY API Client Key 9 | --[no-]color Use colored output, defaults to enabled 10 | --compartment-id COMPARTMENT The OCID of the compartment. 11 | -c, --config CONFIG The configuration file to use 12 | --config-option OPTION=VALUE Override a single configuration option 13 | --defaults Accept default values for all questions 14 | -d, --disable-editing Do not open EDITOR, just accept the data as is 15 | -e, --editor EDITOR Set the editor to use for interactive commands 16 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 17 | --[no-]fips Enable fips mode 18 | -F, --format FORMAT Which format to use for output 19 | --instance-id INSTANCE The OCID of the instance to be deleted. (required) 20 | --[no-]listen Whether a local mode (-z) server binds to a port 21 | -z, --local-mode Point knife commands at local repository instead of server 22 | -u, --user USER API Client Username 23 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 24 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 25 | --print-after Show the data after a destructive operation 26 | --purge Remove the corresponding node from the Chef Server. The instance display name will be used as the node name, unless --node-name is specified. 27 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 28 | -V, --verbose More verbose output. Use twice for max verbosity 29 | -v, --version Show chef version 30 | --wait SECONDS Wait for the instance to be terminated. 0=infinite 31 | -y, --yes Say yes to all prompts for confirmation 32 | -h, --help Show this message 33 | -------------------------------------------------------------------------------- /test_output/help/server_list.txt: -------------------------------------------------------------------------------- 1 | knife oci server list --help 2 | 3 | knife oci server list (options) 4 | -s, --server-url URL Chef Server URL 5 | --chef-zero-host HOST Host to start chef-zero on 6 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 7 | -k, --key KEY API Client Key 8 | --[no-]color Use colored output, defaults to enabled 9 | --compartment-id COMPARTMENT The OCID of the compartment. 10 | -c, --config CONFIG The configuration file to use 11 | --config-option OPTION=VALUE Override a single configuration option 12 | --defaults Accept default values for all questions 13 | -d, --disable-editing Do not open EDITOR, just accept the data as is 14 | -e, --editor EDITOR Set the editor to use for interactive commands 15 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 16 | --[no-]fips Enable fips mode 17 | -F, --format FORMAT Which format to use for output 18 | --limit LIMIT The maximum number of items to return. 19 | --[no-]listen Whether a local mode (-z) server binds to a port 20 | -z, --local-mode Point knife commands at local repository instead of server 21 | -u, --user USER API Client Username 22 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 23 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 24 | --print-after Show the data after a destructive operation 25 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 26 | -V, --verbose More verbose output. Use twice for max verbosity 27 | -v, --version Show chef version 28 | -y, --yes Say yes to all prompts for confirmation 29 | -h, --help Show this message 30 | -------------------------------------------------------------------------------- /test_output/help/shape_list.txt: -------------------------------------------------------------------------------- 1 | knife oci shape list --help 2 | 3 | knife oci shape list (options) 4 | --availability-domain AD The Availability Domain of the instance. 5 | -s, --server-url URL Chef Server URL 6 | --chef-zero-host HOST Host to start chef-zero on 7 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 8 | -k, --key KEY API Client Key 9 | --[no-]color Use colored output, defaults to enabled 10 | --compartment-id COMPARTMENT The OCID of the compartment. 11 | -c, --config CONFIG The configuration file to use 12 | --config-option OPTION=VALUE Override a single configuration option 13 | --defaults Accept default values for all questions 14 | -d, --disable-editing Do not open EDITOR, just accept the data as is 15 | -e, --editor EDITOR Set the editor to use for interactive commands 16 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 17 | --[no-]fips Enable fips mode 18 | -F, --format FORMAT Which format to use for output 19 | --image-id IMAGE The OCID of the image used to boot the instance. 20 | --limit LIMIT The maximum number of items to return. 21 | --[no-]listen Whether a local mode (-z) server binds to a port 22 | -z, --local-mode Point knife commands at local repository instead of server 23 | -u, --user USER API Client Username 24 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 25 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 26 | --print-after Show the data after a destructive operation 27 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 28 | -V, --verbose More verbose output. Use twice for max verbosity 29 | -v, --version Show chef version 30 | -y, --yes Say yes to all prompts for confirmation 31 | -h, --help Show this message 32 | -------------------------------------------------------------------------------- /test_output/help/subnet_list.txt: -------------------------------------------------------------------------------- 1 | knife oci subnet list --help 2 | 3 | knife oci subnet list (options) 4 | -s, --server-url URL Chef Server URL 5 | --chef-zero-host HOST Host to start chef-zero on 6 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 7 | -k, --key KEY API Client Key 8 | --[no-]color Use colored output, defaults to enabled 9 | --compartment-id COMPARTMENT The OCID of the compartment. 10 | -c, --config CONFIG The configuration file to use 11 | --config-option OPTION=VALUE Override a single configuration option 12 | --defaults Accept default values for all questions 13 | -d, --disable-editing Do not open EDITOR, just accept the data as is 14 | -e, --editor EDITOR Set the editor to use for interactive commands 15 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 16 | --[no-]fips Enable fips mode 17 | -F, --format FORMAT Which format to use for output 18 | --limit LIMIT The maximum number of items to return. 19 | --[no-]listen Whether a local mode (-z) server binds to a port 20 | -z, --local-mode Point knife commands at local repository instead of server 21 | -u, --user USER API Client Username 22 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 23 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 24 | --print-after Show the data after a destructive operation 25 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 26 | --vcn-id VCN The VCN ID to list subnets for. (required) 27 | -V, --verbose More verbose output. Use twice for max verbosity 28 | -v, --version Show chef version 29 | -y, --yes Say yes to all prompts for confirmation 30 | -h, --help Show this message 31 | -------------------------------------------------------------------------------- /test_output/help/vcn_list.txt: -------------------------------------------------------------------------------- 1 | knife oci vcn list --help 2 | 3 | knife oci vcn list (options) 4 | -s, --server-url URL Chef Server URL 5 | --chef-zero-host HOST Host to start chef-zero on 6 | --chef-zero-port PORT Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works. 7 | -k, --key KEY API Client Key 8 | --[no-]color Use colored output, defaults to enabled 9 | --compartment-id COMPARTMENT The OCID of the compartment. 10 | -c, --config CONFIG The configuration file to use 11 | --config-option OPTION=VALUE Override a single configuration option 12 | --defaults Accept default values for all questions 13 | -d, --disable-editing Do not open EDITOR, just accept the data as is 14 | -e, --editor EDITOR Set the editor to use for interactive commands 15 | -E, --environment ENVIRONMENT Set the Chef environment (except for in searches, where this will be flagrantly ignored) 16 | --[no-]fips Enable fips mode 17 | -F, --format FORMAT Which format to use for output 18 | --limit LIMIT The maximum number of items to return. 19 | --[no-]listen Whether a local mode (-z) server binds to a port 20 | -z, --local-mode Point knife commands at local repository instead of server 21 | -u, --user USER API Client Username 22 | --oci-config-file FILE The path to the OCI config file. Default: ~/.oci/config 23 | --oci-profile PROFILE The profile to load from the OCI config file. Default: DEFAULT 24 | --print-after Show the data after a destructive operation 25 | --region REGION The region to make calls against. (e.g., `us-ashburn-1`) 26 | -V, --verbose More verbose output. Use twice for max verbosity 27 | -v, --version Show chef version 28 | -y, --yes Say yes to all prompts for confirmation 29 | -h, --help Show this message 30 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: ruby:2.4.1 2 | build: 3 | steps: 4 | - bundle-install 5 | - script: 6 | name: rspec 7 | code: bundle exec rspec spec/unit 8 | 9 | --------------------------------------------------------------------------------