@ -28,9 +28,6 @@ description:
Only supports HTTP Basic Auth
Only supports ' strategic merge ' for update , http : / / goo . gl / fCPYxT
SSL certs are not working , use ' validate_certs=off ' to disable
This module can mimic the ' kubectl ' Kubernetes client for commands
such as ' get ' , ' cluster-info ' , and ' version ' . This is useful if you
want to fetch full object details for existing Kubernetes resources .
options :
api_endpoint :
description :
@ -40,12 +37,14 @@ options:
aliases : [ " endpoint " ]
inline_data :
description :
- The Kubernetes YAML data to send to the API I ( endpoint ) .
- The Kubernetes YAML data to send to the API I ( endpoint ) . This option is
mutually exclusive with C ( ' file_reference ' ) .
required : true
default : null
file_reference :
description :
- Specify full path to a Kubernets YAML file to send to API I ( endpoint ) .
This option is mutually exclusive with C ( ' inline_data ' ) .
required : false
default : null
certificate_authority_data :
@ -56,53 +55,27 @@ options:
' match_hostname ' that can match the IP address against the CA data .
required : false
default : null
kubectl_api_versions :
description :
- Mimic the ' kubectl api-versions ' command , values are ignored .
required : false
default : null
kubectl_cluster_info :
description :
- Mimic the ' kubectl cluster-info ' command , values are ignored .
required : false
default : null
kubectl_get :
description :
- Mimic the ' kubectl get ' command . Specify the object ( s ) to fetch such
as ' pods ' or ' replicationcontrollers/mycontroller ' . It does not
support shortcuts ( e . g . ' po ' , ' rc ' , ' svc ' ) .
required : false
default : null
kubectl_namespace :
description :
- Specify the namespace to use for ' kubectl ' commands .
required : false
default : " default "
kubectl_version :
description :
- Mimic the ' kubectl version ' command , values are ignored .
required : false
default : null
state :
description :
- The desired action to take on the Kubernetes data , or ' kubectl ' to
mimic some kubectl commands .
- The desired action to take on the Kubernetes data .
required : true
default : " present "
choices : [ " present " , " post " , " absent " , " delete " , " update " , " patch " ,
" replace " , " put " , " kubectl " ]
url_password :
choices : [ " present " , " absent " , " update " , " replace " ]
password :
description :
- The HTTP Basic Auth password for the API I ( endpoint ) .
required : true
- The HTTP Basic Auth password for the API I ( endpoint ) . This should be set
unless using the C ( ' insecure ' ) option .
default : null
aliases : [ " password " , " api_password " ]
url_username :
username :
description :
- The HTTP Basic Auth username for the API I ( endpoint ) .
required : true
- The HTTP Basic Auth username for the API I ( endpoint ) . This should be set
unless using the C ( ' insecure ' ) option .
default : " admin "
aliases : [ " username " , " api_username " ]
insecure :
description :
- " Reverts the connection to using HTTP instead of HTTPS. This option should
only be used when execuing the M ( ' kubernetes ' ) module local to the Kubernetes
cluster using the insecure local port ( locahost : 8080 by default ) . "
validate_certs :
description :
- Enable / disable certificate validation . Note that this is set to
@ -110,7 +83,6 @@ options:
hostname matching ( exists in > = python3 .5 .0 ) .
required : false
default : false
choices : BOOLEANS
author : " Eric Johnson (@erjohnso) <erjohnso@google.com> "
'''
@ -120,8 +92,8 @@ EXAMPLES = '''
- name : Create a kubernetes namespace
kubernetes :
api_endpoint : 123.45 .67 .89
u rl_u sername: admin
url_ password: redacted
u sername: admin
password: redacted
inline_data :
kind : Namespace
apiVersion : v1
@ -139,28 +111,19 @@ EXAMPLES = '''
- name : Create a kubernetes namespace
kubernetes :
api_endpoint : 123.45 .67 .89
u rl_u sername: admin
url_ password: redacted
u sername: admin
password: redacted
file_reference : / path / to / create_namespace . yaml
state : present
# Fetch info about the Kubernets cluster with a fake 'kubectl' command.
- name : Look up cluster info
# Do the same thing, but using the insecure localhost port
- name : Create a kubernetes namespace
kubernetes :
api_endpoint : 123.45 .67 .89
url_username : admin
url_password : redacted
kubectl_cluster_info : 1
state : kubectl
insecure : true
file_reference : / path / to / create_namespace . yaml
state : present
# Fetch info about the Kubernets pods with a fake 'kubectl' command.
- name : Look up pods
kubernetes :
api_endpoint : 123.45 .67 .89
url_username : admin
url_password : redacted
kubectl_get : pods
state : kubectl
'''
RETURN = '''
@ -259,6 +222,7 @@ USER_AGENT = "ansible-k8s-module/0.0.1"
# - Set 'required=true' for certificate_authority_data and ensure that
# ansible's SSLValidationHandler.get_ca_certs() can pick up this CA cert
# - Set 'required=true' for the validate_certs param.
def decode_cert_data ( module ) :
return
d = module . params . get ( " certificate_authority_data " )
@ -270,75 +234,70 @@ def api_request(module, url, method="GET", headers=None, data=None):
body = None
if data :
data = json . dumps ( data )
response , info = fetch_url ( module , url , method = method , headers = headers ,
data = data )
response , info = fetch_url ( module , url , method = method , headers = headers , data = data )
if int ( info [ ' status ' ] ) == - 1 :
module . fail_json ( msg = " Failed to execute the API request: %s " % info [ ' msg ' ] , url = url , method = method , headers = headers )
if response is not None :
body = json . loads ( response . read ( ) )
return info , body
def k8s_kubectl_get ( module , url ) :
req = module . params . get ( " kubectl_get " )
info , body = api_request ( module , url + " / " + req )
def k8s_create_resource ( module , url , data ) :
info , body = api_request ( module , url , method = " POST " , data = data , headers = { " Content-Type " : " application/json " } )
if info [ ' status ' ] == 409 :
name = data [ " metadata " ] . get ( " name " , None )
info , body = api_request ( module , url + " / " + name )
return False , body
elif info [ ' status ' ] > = 400 :
module . fail_json ( msg = " failed to create the resource: %s " % info [ ' msg ' ] , url = url )
return True , body
def k8s_delete_resource ( module , url , data ) :
name = None
if ' metadata ' in data :
name = data [ ' metadata ' ] . get ( ' name ' )
name = data . get ( ' metadata ' , { } ) . get ( ' name ' )
if name is None :
module . fail_json ( msg = " Missing a named resource in object metadata " )
url = url + ' / ' + name
module . fail_json ( msg = " Missing a named resource in object metadata when trying to remove a resource " )
url = url + ' / ' + name
info , body = api_request ( module , url , method = " DELETE " )
if info [ ' status ' ] == 404 :
return False , { }
if info [ ' status ' ] == 200 :
return True , body
module . fail_json ( msg = " %s : fetching URL ' %s ' " % ( info [ ' msg ' ] , url ) )
def k8s_create_resource ( module , url , data ) :
info , body = api_request ( module , url , method = " POST " , data = data )
if info [ ' status ' ] == 409 :
name = data [ " metadata " ] . get ( " name " , None )
info , body = api_request ( module , url + " / " + name )
return False , body
return True , body
return False , " Resource name ' %s ' already absent " % name
elif info [ ' status ' ] > = 400 :
module . fail_json ( msg = " failed to delete the resource ' %s ' : %s " % ( name , info [ ' msg ' ] ) , url = url )
return True , " Successfully deleted resource name ' %s ' " % name
def k8s_replace_resource ( module , url , data ) :
name = None
if ' metadata ' in data :
name = data [ ' metadata ' ] . get ( ' name ' )
name = data . get ( ' metadata ' , { } ) . get ( ' name ' )
if name is None :
module . fail_json ( msg = " Missing a named resource in object metadata " )
url = url + ' / ' + name
module . fail_json ( msg = " Missing a named resource in object metadata when trying to replace a resource " )
info , body = api_request ( module , url , method = " PUT " , data = data )
headers = { " Content-Type " : " application/json " }
url = url + ' / ' + name
info , body = api_request ( module , url , method = " PUT " , data = data , headers = headers )
if info [ ' status ' ] == 409 :
name = data [ " metadata " ] . get ( " name " , None )
info , body = api_request ( module , url + " / " + name )
return False , body
elif info [ ' status ' ] > = 400 :
module . fail_json ( msg = " failed to replace the resource ' %s ' : %s " % ( name , info [ ' msg ' ] ) , url = url )
return True , body
def k8s_update_resource ( module , url , data ) :
name = None
if ' metadata ' in data :
name = data [ ' metadata ' ] . get ( ' name ' )
name = data . get ( ' metadata ' , { } ) . get ( ' name ' )
if name is None :
module . fail_json ( msg = " Missing a named resource in object metadata " )
url = url + ' / ' + name
module . fail_json ( msg = " Missing a named resource in object metadata when trying to update a resource " )
headers = { " Content-Type " : " application/strategic-merge-patch+json " }
info, body = api_request ( module , url , method = " PATCH " , data = data ,
headers = headers )
url = url + ' / ' + name
info , body = api_request ( module , url , method = " PATCH " , data = data , headers = headers )
if info [ ' status ' ] == 409 :
name = data [ " metadata " ] . get ( " name " , None )
info , body = api_request ( module , url + " / " + name )
return False , body
elif info [ ' status ' ] > = 400 :
module . fail_json ( msg = " failed to update the resource ' %s ' : %s " % ( name , info [ ' msg ' ] ) , url = url )
return True , body
@ -347,97 +306,81 @@ def main():
argument_spec = dict (
http_agent = dict ( default = USER_AGENT ) ,
u rl_u sername= dict ( default = " admin " ) ,
url_password= dict ( required = True , no_log = True ) ,
u sername= dict ( default = " admin " ) ,
password= dict ( default = " " , no_log = True ) ,
force_basic_auth = dict ( default = " yes " ) ,
validate_certs = dict ( default = False , choices = BOOLEANS ) ,
validate_certs = dict ( default = False , type = ' bool ' ) ,
certificate_authority_data = dict ( required = False ) ,
# fake 'kubectl' commands
kubectl_api_versions = dict ( required = False ) ,
kubectl_cluster_info = dict ( required = False ) ,
kubectl_get = dict ( required = False ) ,
kubectl_namespace = dict ( required = False , default = " default " ) ,
kubectl_version = dict ( required = False ) ,
# k8s API module variables
insecure = dict ( default = False , type = ' bool ' ) ,
api_endpoint = dict ( required = True ) ,
file_reference = dict ( required = False ) ,
inline_data = dict ( required = False ) ,
state = dict ( default = " present " ,
choices = [ " present " , " post " ,
" absent " , " delete " ,
" update " , " put " ,
" replace " , " patch " ,
" kubectl " ] )
)
state = dict ( default = " present " , choices = [ " present " , " absent " , " update " , " replace " ] )
) ,
mutually_exclusive = ( ( ' file_reference ' , ' inline_data ' ) , ( ' username ' , ' insecure ' ) , ( ' password ' , ' insecure ' ) ) ,
required_one_of = ( ( ' file_reference ' , ' inline_data ' ) , ) ,
)
decode_cert_data ( module )
changed = False
data = module . params . get ( ' inline_data ' , { } )
api_endpoint = module . params . get ( ' api_endpoint ' )
state = module . params . get ( ' state ' )
insecure = module . params . get ( ' insecure ' )
inline_data = module . params . get ( ' inline_data ' )
file_reference = module . params . get ( ' file_reference ' )
if inline_data :
data = inline_data
else :
try :
f = open ( file_reference , " r " )
data = [ x for x in yaml . load_all ( f ) ]
f . close ( )
if not data :
dfile = module . params . get ( ' file_reference ' )
if dfile :
f = open ( dfile , " r " )
data = yaml . load ( f )
module . fail_json ( msg = " No valid data could be found. " )
except :
module . fail_json ( msg = " The file ' %s ' was not found or contained invalid YAML/JSON data " % file_reference )
endpoint = " https:// " + module . params . get ( ' api_endpoint ' )
url = endpoint
# set the transport type and build the target endpoint url
transport = ' https '
if insecure :
transport = ' http '
namespace = " default "
if data and ' metadata ' in data :
namespace = data [ ' metadata ' ] . get ( ' namespace ' , " default " )
kind = data [ ' kind ' ] . lower ( )
url = endpoint + KIND_URL [ kind ]
url = url . replace ( " {namespace} " , namespace )
target_endpoint = " %s :// %s " % ( transport , api_endpoint )
# check for 'kubectl' commands
kubectl_api_versions = module . params . get ( ' kubectl_api_versions ' )
kubectl_cluster_info = module . params . get ( ' kubectl_cluster_info ' )
kubectl_get = module . params . get ( ' kubectl_get ' )
kubectl_namespace = module . params . get ( ' kubectl_namespace ' )
kubectl_version = module . params . get ( ' kubectl_version ' )
body = [ ]
changed = False
state = module . params . get ( ' state ' )
if state in [ ' present ' , ' post ' ] :
changed , body = k8s_create_resource ( module , url , data )
module . exit_json ( changed = changed , api_response = body )
# make sure the data is a list
if not isinstance ( data , list ) :
data = [ data ]
if state in [ ' absent ' , ' delete ' ] :
changed , body = k8s_delete_resource ( module , url , data )
module . exit_json ( changed = changed , api_response = body )
for item in data :
namespace = " default "
if item and ' metadata ' in item :
namespace = item . get ( ' metadata ' , { } ) . get ( ' namespace ' , " default " )
kind = item . get ( ' kind ' , ' ' ) . lower ( )
try :
url = target_endpoint + KIND_URL [ kind ]
except KeyError :
module . fail_json ( " invalid resource kind specified in the data: ' %s ' " % kind )
url = url . replace ( " {namespace} " , namespace )
else :
url = target_endpoint
if state in [ ' replace ' , ' put ' ] :
changed , body = k8s_replace_resource ( module , url , data )
module . exit_json ( changed = changed , api_response = body )
if state == ' present ' :
item_changed , item_body = k8s_create_resource ( module , url , item )
elif state == ' absent ' :
item_changed , item_body = k8s_delete_resource ( module , url , item )
elif state == ' replace ' :
item_changed , item_body = k8s_replace_resource ( module , url , item )
elif state == ' update ' :
item_changed , item_body = k8s_update_resource ( module , url , item )
if state in [ ' update ' , ' patch ' ] :
changed , body = k8s_update_resource ( module , url , data )
module . exit_json ( changed = changed , api_response = body )
changed | = item_changed
body . append ( item_body )
if state == ' kubectl ' :
kurl = url + " /api/v1/namespaces/ " + kubectl_namespace
if kubectl_get :
if kubectl_get . startswith ( " namespaces " ) :
kurl = url + " /api/v1 "
changed , body = k8s_kubectl_get ( module , kurl )
module . exit_json ( changed = changed , api_response = body )
if kubectl_version :
info , body = api_request ( module , url + " /version " )
module . exit_json ( changed = False , api_response = body )
if kubectl_api_versions :
info , body = api_request ( module , url + " /api " )
module . exit_json ( changed = False , api_response = body )
if kubectl_cluster_info :
info , body = api_request ( module , url +
" /api/v1/namespaces/kube-system "
" /services?labelSelector=kubernetes "
" .io/cluster-service=true " )
module . exit_json ( changed = False , api_response = body )
module . fail_json ( msg = " Invalid state: ' %s ' " % state )
# import module snippets