Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,24 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a
Changelog <http://keepachangelog.com/>`__.

2.4.12 - 2017-11-27
-------------------

Added
~~~~~~~~~~
* Support option for using second physical NIC on X7 Bare Metal instances (--nic-index option on 'oci compute instance attach-vnic')
* Support for Local Peering Gateway operations ('oci network local-peering-gateway')
* Support for specifying a default for the --profile option in the oci_cli_rc file
* Support create database from backup (oci db database create-from-backup)
* Support for getting archived object restore status ('oci os object restore-status') more details in sample (https://github.com/oracle/oci-cli/scripts/restore_archived_object.sh)

Changed
~~~~~~~~~~
* Help displayed via the --help/-h/-? option is now formatted like man pages found on Unix (or Unix-like) systems. To switch back to the previous way of displaying help, add `use_click_help = True` to the `OCI_CLI_SETTINGS` section of your oci_cli_rc file

2.4.11 - 2017-11-02
-------------------

Added
~~~~~~~~~~
* 'oci setup oci-cli-rc' command to generate an oci_cli_rc file with default aliases and pre-defined queries
Expand Down
5 changes: 3 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
include LICENSE.txt
include README.rst
include CHANGELOG.md
include CHANGELOG.rst
include src/oci_cli/bin/oci_autocomplete.sh
include src/oci_cli/bin/OciTabExpansion.ps1
exclude setup.cfg
recursive-include src/oci_cli/help_text_producer/data_files *
exclude setup.cfg
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Jinja2==2.9.6
jmespath==0.9.3
ndg-httpsclient==0.4.2
mock==2.0.0
oci==1.3.9
oci==1.3.10
packaging==16.8
pluggy==0.4.0
py==1.4.32
Expand Down
68 changes: 68 additions & 0 deletions scripts/restore_archived_object.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/bin/bash
# Restore an archived object and download it
# The archived object is not available for download initialy. Issue the restore
# command will kick off the process and it will be available for download in about
# 4 hours.
# Example run: ./scripts/restore_archived_object.sh namespace buckect object file

set -e

# Setup
mkdir -p scripts/temp
NS=$1
BUCKET=$2
OBJECT=$3
FILE=$4

# Show time elapsed in waiting for object restoring to be completed
function show_time_elapsed () {
num=$SECONDS
min=0
hour=0
if((num>59));then
((sec=num%60))
((num=num/60))
if((num>59));then
((min=num%60))
((num=num/60))
((hour=num))
else
((min=num))
fi
else
((sec=num))
fi
echo -ne "Restoring time elapsed: $hour"h "$min"m "$sec"s.\\r
}

# Get object's restore status
STATUS=$(oci os object restore-status -ns $NS -bn $BUCKET --name $OBJECT 2>&1)
echo $STATUS

# Object is archived, call restore command to start the restoring process
if [[ $STATUS == Archived* ]] ;
then
echo "Archived, restore the object"
oci os object restore -ns $NS -bn $BUCKET --name $OBJECT
STATUS=$(oci os object restore-status -ns $NS -bn $BUCKET --name $OBJECT 2>&1)
fi

# Object is in restoring process it could take up to 4 hours to be available for download
# Pulling every 10 minutes to check the status
if [[ $STATUS == Restoring* ]] ;
then
SECONDS=0
while [[ $STATUS == Restoring* ]]
do
STATUS=$(oci os object restore-status -ns $NS -bn $BUCKET --name $OBJECT 2>&1)
show_time_elapsed
sleep 600
done
fi

# Object is available for download, go ahead download it
if [[ $STATUS == Available* ]] || [[ $STATUS == Restored* ]] ;
then
oci os object get -ns $NS -bn $BUCKET --name $OBJECT --file $FILE
echo "File is downloaded to $FILE"
fi
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def open_relative(*path):


requires = [
'oci==1.3.9',
'oci==1.3.10',
'arrow==0.10.0',
'certifi',
'click==6.7',
Expand Down
1 change: 1 addition & 0 deletions src/oci_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@
from . import retry_utils # noqa: F401
from . import json_skeleton_utils # noqa: F401
from . import string_utils # noqa: F401
from . import help_text_producer # noqa: F401
from oci import config # noqa: F401
from .version import __version__ # noqa: F401
177 changes: 127 additions & 50 deletions src/oci_cli/bin/OciTabExpansion.ps1

Large diffs are not rendered by default.

66 changes: 56 additions & 10 deletions src/oci_cli/cli_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
# Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.

import sys
from oci.config import DEFAULT_LOCATION
from oci.config import DEFAULT_LOCATION, DEFAULT_PROFILE
import click
import configparser
import os.path
import logging
from oci.util import Sentinel
import six

from .version import __version__

from .aliasing import parameter_alias, CommandGroupWithAlias

from . import help_text_producer
from . import cli_util

# Enable WARN logging to surface important warnings attached to loading
# defaults, automatic coercion, or fallback values/endpoints that may impact
Expand All @@ -34,6 +36,10 @@
CLI_RC_CANNED_QUERIES_SECTION_NAME = 'OCI_CLI_CANNED_QUERIES'
CLI_RC_COMMAND_ALIASES_SECTION_NAME = 'OCI_CLI_COMMAND_ALIASES'
CLI_RC_PARAM_ALIASES_SECTION_NAME = 'OCI_CLI_PARAM_ALIASES'
CLI_RC_GENERIC_SETTINGS_SECTION_NAME = 'OCI_CLI_SETTINGS'

CLI_RC_GENERIC_SETTINGS_DEFAULT_PROFILE_KEY = 'default_profile'
CLI_RC_GENERIC_SETTINGS_USE_CLICK_HELP = 'use_click_help'


def eager_load_cli_rc_file(ctx, param, value):
Expand All @@ -45,7 +51,8 @@ def eager_load_cli_rc_file(ctx, param, value):
'canned_queries': {},
'global_command_alias': {},
'command_sequence_alias': {},
'parameter_aliases': {}
'parameter_aliases': {},
'settings': {}
}

# Try and find the configuration file. This is checked in the following order:
Expand All @@ -59,27 +66,39 @@ def eager_load_cli_rc_file(ctx, param, value):
parser_without_defaults = configparser.ConfigParser(interpolation=None, default_section=None) # Don't use DEFAULT as the default section, so this doesn't bring in any extra stuff
if os.path.exists(file_location):
parser_without_defaults.read(file_location)
populate_aliases_and_canned_queries(ctx, parser_without_defaults)
populate_aliases_canned_queries_and_settings(ctx, parser_without_defaults)

return file_location
elif os.path.exists(expanded_rc_default_location):
parser_without_defaults.read(expanded_rc_default_location)
populate_aliases_and_canned_queries(ctx, parser_without_defaults)
populate_aliases_canned_queries_and_settings(ctx, parser_without_defaults)

return expanded_rc_default_location
elif os.path.exists(expanded_rc_fallback_location):
parser_without_defaults.read(expanded_rc_fallback_location)
populate_aliases_and_canned_queries(ctx, parser_without_defaults)
populate_aliases_canned_queries_and_settings(ctx, parser_without_defaults)

return expanded_rc_fallback_location
else:
return value


def populate_aliases_and_canned_queries(ctx, parser_without_defaults):
def populate_aliases_canned_queries_and_settings(ctx, parser_without_defaults):
populate_canned_queries(ctx, parser_without_defaults)
populate_command_aliases(ctx, parser_without_defaults)
populate_parameter_aliases(ctx, parser_without_defaults)
populate_settings(ctx, parser_without_defaults)


def populate_settings(ctx, parser_without_defaults):
raw_settings = get_section_without_defaults(parser_without_defaults, CLI_RC_GENERIC_SETTINGS_SECTION_NAME)

settings = {}
if raw_settings:
for setting in raw_settings:
settings[setting[0]] = setting[1]

ctx.obj['settings'] = settings


def populate_command_aliases(ctx, parser_without_defaults):
Expand Down Expand Up @@ -178,8 +197,8 @@ def get_section_without_defaults(parser_without_defaults, section_name):
default=DEFAULT_LOCATION, show_default=True,
help='The path to the config file.')
@click.option('--profile',
default='DEFAULT', show_default=True,
help='The profile in the config file to load. This profile will also be used to locate any default parameter values which have been specified in the OCI CLI-specific configuration file')
default=Sentinel(DEFAULT_PROFILE), show_default=False,
help='The profile in the config file to load. This profile will also be used to locate any default parameter values which have been specified in the OCI CLI-specific configuration file. [default: DEFAULT]')
@click.option('--cli-rc-file', '--defaults-file',
default=CLI_RC_DEFAULT_LOCATION, show_default=True,
is_eager=True, callback=eager_load_cli_rc_file,
Expand All @@ -206,6 +225,15 @@ def cli(ctx, config_file, profile, defaults_file, request_id, region, endpoint,
click.echo(ctx.get_help(), color=ctx.color)
ctx.exit()

if profile == Sentinel(DEFAULT_PROFILE):
# if --profile is not supplied, check if default_profile is specified in oci_cli_rc and use it if present
# --profile cannot be specified as a regular default because we use it to determine which
# section of the default file to read from
if 'settings' in ctx.obj and CLI_RC_GENERIC_SETTINGS_DEFAULT_PROFILE_KEY in ctx.obj['settings']:
profile = ctx.obj['settings'][CLI_RC_GENERIC_SETTINGS_DEFAULT_PROFILE_KEY]
else:
profile = DEFAULT_PROFILE

initial_dict = {
'config_file': config_file,
'profile': profile,
Expand All @@ -228,6 +256,24 @@ def cli(ctx, config_file, profile, defaults_file, request_id, region, endpoint,

if help:
ctx.obj['help'] = True
if is_top_level_help(ctx) and not cli_util.parse_boolean(ctx.obj.get('settings', {}).get(CLI_RC_GENERIC_SETTINGS_USE_CLICK_HELP, False)):
help_text_producer.render_help_text(ctx, [sys.argv[1]])


def is_top_level_help(ctx):
if len(sys.argv) != 3:
return False

top_level_command_tuples = []
for cmd_name, cmd_obj in six.iteritems(ctx.command.commands):
if isinstance(cmd_obj, click.Group):
top_level_command_tuples.append((cmd_name, cmd_obj))

for cmd_tuple in top_level_command_tuples:
if cmd_tuple[0] == sys.argv[1] and sys.argv[2] in ['-?', '-h', '--help']:
return True

return False


def load_default_values(ctx, defaults_file, profile):
Expand Down
3 changes: 2 additions & 1 deletion src/oci_cli/cli_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import click
from .cli_root import cli, CLI_RC_CANNED_QUERIES_SECTION_NAME, CLI_RC_COMMAND_ALIASES_SECTION_NAME, CLI_RC_PARAM_ALIASES_SECTION_NAME
from . import cli_util
from .cli_root import CLI_RC_DEFAULT_LOCATION

import base64
import hashlib
Expand Down Expand Up @@ -232,7 +233,7 @@ def generate_oci_config():

This command will populate the file with some default aliases and predefined queries.
""")
@click.option('--file', type=click.File(mode='a+b'), required=True, help="The file into which default aliases and predefined queries will be loaded")
@click.option('--file', default=os.path.expanduser(CLI_RC_DEFAULT_LOCATION), type=click.File(mode='a+b'), required=True, help="The file into which default aliases and predefined queries will be loaded")
@cli_util.help_option
def setup_cli_rc(file):
if hasattr(file, 'name') and file.name == '<stdout>':
Expand Down
35 changes: 34 additions & 1 deletion src/oci_cli/cli_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from .version import __version__

from . import string_utils
from . import help_text_producer

try:
# PY3+
Expand Down Expand Up @@ -599,17 +600,39 @@ def filter_object_headers(headers, whitelist):


def help_callback(ctx, param, value):
from . import cli_root
if ctx.obj.get("help", False):
if not parse_boolean(ctx.obj.get('settings', {}).get(cli_root.CLI_RC_GENERIC_SETTINGS_USE_CLICK_HELP, False)):
help_text_producer.render_help_text(ctx)

# We should only fall down here if the man/text-formatted help is unavailable or if the customer wanted
# the click help
click.echo(ctx.get_help(), color=ctx.color)
ctx.exit()


def group_help_callback(ctx, param, value):
from . import cli_root
args = sys.argv[1:]
filtered_args = []
for a in args:
if not a.startswith('-'):
filtered_args.append(a)

# It is OK to not have an alternate path here (e.g. if help_text_producer did nothing and didn't exit) because
# we'll just fall back to click's handling of group help. Note that using ctx.get_help() directly doesn't
# work in this group help scenario, so we have to rely on click to do the right thing
if ctx.obj.get("help", False):
if not parse_boolean(ctx.obj.get('settings', {}).get(cli_root.CLI_RC_GENERIC_SETTINGS_USE_CLICK_HELP, False)):
help_text_producer.render_help_text(ctx, filtered_args)


'''Help option to use for commands.'''
help_option = click.option('-?', '-h', '--help', is_flag=True, help='Show this message and exit.', expose_value=False, is_eager=True, callback=help_callback)


'''Help option to use for groups (except for oci).'''
help_option_group = click.help_option('-?', '-h', '--help', help='Show this message and exit.')
help_option_group = click.option('-?', '-h', '--help', is_flag=True, help='Show this message and exit.', expose_value=False, is_eager=False, callback=group_help_callback)


def confirmation_callback(ctx, param, value):
Expand Down Expand Up @@ -1042,3 +1065,13 @@ def resolve_jmespath_query(ctx, query):
raise click.UsageError('Query {} is not defined in your OCI CLI configuration file: {}'.format(query_name, ctx.obj['defaults_file']))
else:
return query


def parse_boolean(obj):
if not str:
return False

if isinstance(obj, bool):
return obj

return str(obj).lower() in DEFAULT_FILE_CONVERT_PARAM_TRUTHY_VALUES
6 changes: 5 additions & 1 deletion src/oci_cli/core_cli_extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
virtualnetwork_cli.virtual_network_group.add_command(virtualnetwork_cli.cpe_group)
virtualnetwork_cli.virtual_network_group.add_command(virtualnetwork_cli.security_list_group)
virtualnetwork_cli.virtual_network_group.add_command(virtualnetwork_cli.private_ip_group)
virtualnetwork_cli.virtual_network_group.add_command(virtualnetwork_cli.local_peering_gateway_group)
virtualnetwork_cli.private_ip_group.commands.pop(virtualnetwork_cli.create_private_ip.name)
virtualnetwork_cli.private_ip_group.commands.pop(virtualnetwork_cli.update_private_ip.name)

Expand Down Expand Up @@ -563,6 +564,7 @@ def launch_instance_extended(ctx, **kwargs):
@click.option('--skip-source-dest-check', required=False, type=click.BOOL, help="""Indicates whether Source/Destination check is disabled on the VNIC. Defaults to `false`, in which case we enable Source/Destination check on the VNIC.""")
@click.option('--private-ip', required=False, help="""A private IP address of your choice to assign to the VNIC. Must be an available IP address within the subnet's CIDR. If no value is specified, a private IP address from the subnet will be automatically assigned.""")
@click.option('--hostname-label', help="""The hostname for the VNIC. Used for DNS. The value is the hostname portion of the VNIC's fully qualified domain name (FQDN) (e.g., `bminstance-1` in FQDN `bminstance-1.subnet123.vcn1.oraclevcn.com`). Must be unique across all VNICs in the subnet and comply with [RFC 952](https://tools.ietf.org/html/rfc952) and [RFC 1123](https://tools.ietf.org/html/rfc1123). The value can be retrieved from the [Vnic](#/en/iaas/20160918/Vnic/).""")
@click.option('--nic-index', required=False, type=click.INT, help="""Which physical network interface card (NIC) the VNIC will use. Defaults to 0. Certain bare metal instance shapes have two active physical NICs (0 and 1). If you add a secondary VNIC to one of these instances, you can specify which NIC the VNIC will use.""")
@click.option('--wait', is_flag=True, default=False, help="""If set, then wait for the attachment to complete and return the newly attached VNIC. If not set, then the command will not wait and will return nothing on success.""")
@click.option('--generate-full-command-json-input', is_flag=True, is_eager=True, callback=json_skeleton_utils.generate_json_skeleton_click_callback, help="""Prints out a JSON document which represents all possible options that can be provided to this command.

Expand All @@ -575,7 +577,7 @@ def launch_instance_extended(ctx, **kwargs):
@click.pass_context
@json_skeleton_utils.json_skeleton_wrapper_metadata(input_params_to_complex_types={}, output_type={'module': 'core', 'class': 'Vnic'})
@cli_util.wrap_exceptions
def attach_vnic(ctx, generate_full_command_json_input, generate_param_json_input, from_json, instance_id, subnet_id, vnic_display_name, assign_public_ip, private_ip, skip_source_dest_check, hostname_label, wait):
def attach_vnic(ctx, generate_full_command_json_input, generate_param_json_input, from_json, instance_id, subnet_id, vnic_display_name, assign_public_ip, private_ip, skip_source_dest_check, hostname_label, nic_index, wait):
if generate_param_json_input and generate_full_command_json_input:
raise click.UsageError("Cannot specify both the --generate-full-command-json-input and --generate-param-json-input parameters")

Expand All @@ -588,6 +590,7 @@ def attach_vnic(ctx, generate_full_command_json_input, generate_param_json_input
instance_id = cli_util.coalesce_provided_and_default_value(ctx, 'instance-id', instance_id, True)
subnet_id = cli_util.coalesce_provided_and_default_value(ctx, 'subnet-id', subnet_id, True)
vnic_display_name = cli_util.coalesce_provided_and_default_value(ctx, 'vnic-display-name', vnic_display_name, False)
nic_index = cli_util.coalesce_provided_and_default_value(ctx, 'nic-index', nic_index, False)
assign_public_ip = cli_util.coalesce_provided_and_default_value(ctx, 'assign-public-ip', assign_public_ip, False)
skip_source_dest_check = cli_util.coalesce_provided_and_default_value(ctx, 'skip-source-dest-check', skip_source_dest_check, False)
private_ip = cli_util.coalesce_provided_and_default_value(ctx, 'private-ip', private_ip, False)
Expand All @@ -609,6 +612,7 @@ def attach_vnic(ctx, generate_full_command_json_input, generate_param_json_input
attachment_details = {}
attachment_details['createVnicDetails'] = vnic_details
attachment_details['instanceId'] = instance_id
attachment_details['nicIndex'] = nic_index

compute_client = cli_util.build_client('compute', ctx)
response = compute_client.attach_vnic(
Expand Down
Loading