Skip to content

Commit 60836b6

Browse files
kubectl appurl plugins update (#1420)
1 parent 3882bac commit 60836b6

File tree

7 files changed

+103
-175
lines changed

7 files changed

+103
-175
lines changed

examples/multitenancy/application-hosting/odoo/steps.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ This example shows delivering Bitnami Odoo Helm chart as-a-service using KubePlu
7272
$ kubectl applogs OdooService sample-odooservice -k kubeplus-saas-provider.json
7373

7474
11. Get application URL:
75-
$ appurl=`kubectl appurl OdooService sample-odooservice default -k kubeplus-saas-provider.json`
75+
$ appurl=`kubectl appurl OdooService sample-odooservice -k kubeplus-saas-provider.json`
7676
$ curl $appurl/web/login
7777
- if the installation is successful, the curl call should return 200 OK.
7878

examples/multitenancy/application-hosting/wordpress/steps.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ $ kubectl kubeplus commands
6363

6464
11. Wait till all the wp-tenant1 instance pods are in 'Running' state, then perform following actions:
6565
- Get Wordpress URL
66-
$ kubectl appurl WordpressService wp-tenant1 $KUBEPLUS_NS -k kubeplus-saas-provider.json
66+
$ kubectl appurl WordpressService wp-tenant1 -k kubeplus-saas-provider.json
6767
$ curl -v <the app url output from above command>
6868
- Note that this IP:Port combination will be reachable through your browser only
6969
if you have opened up your Kubernetes cluster to traffic from outside world.

examples/multitenancy/hello-world/saas-and-managed-app-testing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Multi-namespace setup
5959
- Hello World Pod in hs1 namespace
6060
18. kubectl get ns
6161
- hs1 namespace has been created
62-
19. kubectl appurl HelloWorldService hs1 default -k consumer.conf
62+
19. kubectl appurl HelloWorldService hs1 -k consumer.conf
6363
- curl the IP address received. Should see "Hello hello hello"
6464
20. kubectl applogs HelloWorldService hs1 -k consumer.conf
6565
- Should see app logs
@@ -104,7 +104,7 @@ Single namespace setup
104104
16. kubectl create -f hs1.yaml --kubeconfig=provider.conf
105105
17. kubectl get pods -A
106106
- Hello World Pod in kubeplus namespace
107-
18. kubectl appurl HelloWorldService hs1 kubeplus -k provider.conf
107+
18. kubectl appurl HelloWorldService hs1 -k provider.conf
108108
- curl the IP address received. Should see "Hello hello hello"
109109
19. kubectl applogs HelloWorldService hs1 -k provider.conf
110110
- Should see app logs

examples/multitenancy/hello-world/steps.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ You can use either the provider.conf or consumer.conf in below commands.
8282

8383

8484
5. Retrievel application url
85-
- kubectl appurl HelloWorldService hs1 default -k consumer.conf
85+
- kubectl appurl HelloWorldService hs1 -k consumer.conf
8686
- curl <app-url> from above output
8787
- should see "Hello hello hello" displayed
8888

examples/multitenancy/managed-service/appmigration/steps.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
5. kubectl create -f helloappv1.yaml
66
6. kubectl get helloappv1s -o json
77
7. kubectl get pods -n hs1
8-
8. APP1_URL=`kubectl appurl HelloAppV1 hs1 default -k kubeplus-saas-provider.json`
8+
8. APP1_URL=`kubectl appurl HelloAppV1 hs1 -k kubeplus-saas-provider.json`
99
9. curl $APP1_URL
1010
10. kubectl create -f helloappv1-1.yaml
1111
11. kubectl get helloappv2s -o json
1212
12. kubectl get pods -n hs1
13-
13. APP2_URL=`kubectl appurl HelloAppV2 hs1 default -k kubeplus-saas-provider.json`
13+
13. APP2_URL=`kubectl appurl HelloAppV2 hs1 -k kubeplus-saas-provider.json`
1414
14. curl $APP2_URL
1515
15. kubectl describe helloappv1s
1616
16. kubectl delete -f helloappv1.yaml

plugins/appurl.py

Lines changed: 90 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -8,155 +8,103 @@
88

99
class AppURLFinder(CRBase):
1010

11-
def get_ingresses(self, resources):
12-
ingress_list = []
13-
for resource in resources:
14-
#print(resource)
15-
if resource['Kind'] == 'Ingress':
16-
present = False
17-
for s in ingress_list:
18-
if s['Name'] == resource['Name']:
19-
present = True
20-
break
21-
if not present:
22-
ingress_list.append(resource)
23-
#print(ingress_list)
24-
return ingress_list
11+
def _get_node_ips(self, kubeconfig):
12+
node_ip_types = []
13+
node_ips = []
14+
node_list = []
2515

26-
def get_svc(self, resources):
27-
svc_list = []
28-
for resource in resources:
29-
#print(resource)
30-
if resource['Kind'] == 'Service':
31-
present = False
32-
for s in svc_list:
33-
if s['Name'] == resource['Name']:
34-
present = True
35-
break
36-
if not present:
37-
svc_list.append(resource)
38-
#print(svc_list)
39-
return svc_list
16+
# get all the nodes
17+
get_nodes = "kubectl get nodes " + kubeconfig
18+
out, err = self.run_command(get_nodes)
19+
for line in out.split("\n"):
20+
line1 = ' '.join(line.split())
21+
if 'NAME' not in line1 and line1 != '':
22+
parts = line1.split(" ")
23+
nodename = parts[0].strip()
24+
node_list.append(nodename)
4025

41-
def get_host_from_ingress(self, ingresses, namespace, kubeconfig):
42-
appURL = ""
43-
for ingress in ingresses:
44-
cmd = 'kubectl get ingress ' + ingress['Name'] + ' -n ' + ingress['Namespace'] + ' -o json ' + kubeconfig
45-
#print(cmd)
46-
try:
47-
out = subprocess.Popen(cmd, stdout=subprocess.PIPE,
48-
stderr=subprocess.PIPE, shell=True).communicate()[0]
26+
# for each node, build list of external and internal ips
27+
for node in node_list:
28+
describe_node = "kubectl describe node " + node + " " + kubeconfig
29+
out1, err1 = self.run_command(describe_node)
30+
ip_types = {}
31+
for line in out1.split("\n"):
32+
line1 = ' '.join(line.split())
33+
if 'ExternalIP' in line1:
34+
parts = line1.split(":")
35+
nodeIP = parts[1].strip()
36+
ip_types["external"] = nodeIP
37+
if 'InternalIP' in line1:
38+
parts = line1.split(":")
39+
nodeIP = parts[1].strip()
40+
ip_types["internal"] = nodeIP
41+
node_ip_types.append(ip_types)
4942

50-
if out:
51-
json_output = json.loads(out)
52-
#print(json_output)
53-
if 'tls' in json_output['spec']:
54-
host = json_output['spec']['tls'][0]['hosts'][0]
55-
appURL = "https://" + host.strip()
56-
else:
57-
host = json_output['spec']['rules'][0]['host']
58-
appURL = "http://" + host.strip()
59-
break
60-
except Exception as e:
61-
print(e)
62-
return appURL
43+
# prefer external ip; otherwise return internal ip
44+
for item in node_ip_types:
45+
if "external" in item:
46+
node_ips.append(item["external"])
47+
elif "internal" in item:
48+
node_ips.append(item["internal"])
6349

64-
def get_svc_port(self, svcs, namespace, kubeconfig):
65-
nodePort = -1
66-
for svc in svcs:
67-
cmd = 'kubectl get service ' + svc['Name'] + ' -n ' + svc['Namespace'] + ' -o json ' + kubeconfig
68-
#print(cmd)
69-
try:
70-
out = subprocess.Popen(cmd, stdout=subprocess.PIPE,
71-
stderr=subprocess.PIPE, shell=True).communicate()[0]
50+
return node_ips
7251

73-
if out:
74-
json_output = json.loads(out)
75-
#print(json_output)
76-
service_type = json_output['spec']['type']
77-
if service_type == 'NodePort':
78-
nodePort = json_output['spec']['ports'][0]['nodePort']
79-
#print("NodePort:" + str(nodePort))
80-
break
81-
except Exception as e:
82-
print(e)
83-
return nodePort
8452

85-
def get_node_ip(self, kubeconfig):
86-
cmd = 'kubectl describe node ' + kubeconfig
87-
out = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0]
88-
out = out.decode('utf-8')
89-
#print("Node o/p:" + out)
90-
for line in out.split("\n"):
91-
if 'ExternalIP' in line:
92-
parts = line.split(":")
93-
nodeIP = parts[1].strip()
94-
#print("Node IP:" + nodeIP)
95-
return nodeIP
96-
return ""
53+
def get_service_endpoints(self, kind, service_instance_name, kubeconfig):
54+
endpoints = []
55+
service_name_in_annotation = self.get_service_name_from_ns(service_instance_name, kubeconfig)
9756

98-
def get_server_ip(self, kubeconfig):
99-
server_ip = ''
100-
parts = kubeconfig.split("=")
101-
kcfg = parts[1].strip()
102-
#print(parts)
103-
fp = open(kcfg, "r")
104-
#fp = open("/root/.kube/config", "r")
105-
contents = fp.read()
106-
content = ''
107-
try:
108-
content = json.loads(contents)
109-
except:
110-
content = yaml.safe_load(contents)
57+
if service_name_in_annotation.lower() != kind.lower():
58+
print("Instance does not belong to the Service:" + kind)
59+
sys.exit(1)
60+
61+
ingress_cmd = "kubectl get ingress -n " + service_instance_name + " " + kubeconfig
62+
63+
out, err = self.run_command(ingress_cmd)
64+
if "No resources found" not in err:
65+
for line in out.split("\n"):
66+
line1 = ' '.join(line.split())
67+
if 'NAME' not in line1 and line1 != '':
68+
parts = line1.split(" ")
69+
hostname = parts[2].strip()
70+
protocol = "https://"
71+
endpoint = protocol + hostname
72+
endpoints.append(endpoint)
73+
else:
74+
service_cmd = "kubectl get service -n " + service_instance_name + " " + kubeconfig
75+
out1, err1 = self.run_command(service_cmd)
76+
for line in out1.split("\n"):
77+
line1 = ' '.join(line.split())
78+
endpoint = ''
79+
if 'NAME' not in line1 and line1 != '':
80+
parts = line1.split(" ")
81+
port_parts = parts[4].split(",")
82+
for protocol in port_parts:
83+
proto_ports = protocol.split(":")
84+
if len(proto_ports) == 2:
85+
proto_port = proto_ports[0].strip()
86+
app_port = (proto_ports[1].strip()).split("/")[0].strip()
87+
if parts[1] == 'LoadBalancer':
88+
if proto_port == '80':
89+
endpoint = "http://" + parts[2].strip() + ":" + app_port
90+
if proto_port == '443':
91+
endpoint = "https://" + parts[2].strip() + ":" + app_port
92+
if parts[1] == 'NodePort':
93+
ip_address_list = self._get_node_ips(kubeconfig)
94+
for ipaddr in ip_address_list:
95+
if proto_port == '80':
96+
endpoint = "http://" + ipaddr.strip() + ":" + app_port
97+
if proto_port == '443':
98+
endpoint = "https://" + ipaddr.strip() + ":" + app_port
99+
endpoints.append(endpoint)
100+
101+
return endpoints
111102

112-
cluster_list = content['clusters']
113-
for cluster in cluster_list:
114-
cluster_name = cluster['name']
115-
#print("Cluster name:" + cluster_name)
116-
if cluster_name == 'kubeplus-saas-consumer' or cluster_name == 'kubeplus-saas-provider':
117-
server_url = cluster['cluster']['server']
118-
#print(server_url)
119-
server_url = server_url.strip()
120-
parts = server_url.split(":")
121-
server_ip = parts[1].strip()
122-
#print(server_ip)
123-
return server_ip
124103

125104
if __name__ == '__main__':
126-
appURLFinder = AppURLFinder()
127-
#crLogs.get_logs(sys.argv[1], sys.argv[2])
128-
#resources = sys.argv[1]
129-
relation = sys.argv[1]
130-
kind = sys.argv[2]
131-
instance = sys.argv[3]
132-
namespace = sys.argv[4]
133-
kubeconfig = sys.argv[5]
134-
#print(kind + " " + instance + " " + namespace + " " + kubeconfig)
135-
resources = {}
136-
if relation == 'connections':
137-
resources = appURLFinder.get_resources_connections(kind, instance, namespace, kubeconfig)
138-
#print(resources)
139-
try:
140-
ingresses = appURLFinder.get_ingresses(resources)
141-
if len(ingresses) > 0:
142-
appURL = appURLFinder.get_host_from_ingress(ingresses, namespace, kubeconfig)
143-
print(appURL)
144-
else:
145-
svcs = appURLFinder.get_svc(resources)
146-
svcPort = appURLFinder.get_svc_port(svcs, namespace, kubeconfig)
147-
appIP = appURLFinder.get_node_ip(kubeconfig)
148-
if appIP == "":
149-
appIP = appURLFinder.get_server_ip(kubeconfig)
150-
if appIP == '':
151-
print("KubePlus SaaS Consumer context not found in the kubeconfig.")
152-
print("Cannot form app url.")
153-
exit()
154-
else:
155-
if "//" not in appIP:
156-
appIP = "//" + appIP
157-
appURL = "http:" + appIP + ":" + str(svcPort)
158-
appURL = appURL.strip()
159-
#print("App port:" + str(svcPort))
160-
print(appURL)
161-
except Exception as e:
162-
print(e)
105+
appURLFinder = AppURLFinder()
106+
kind = sys.argv[1]
107+
instance = sys.argv[2]
108+
kubeconfig = sys.argv[3]
109+
endpoints = appURLFinder.get_service_endpoints(kind, instance, kubeconfig)
110+
print(str(endpoints))

plugins/kubectl-appurl

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,21 @@ print_help () {
77
echo " kubectl appurl"
88
echo ""
99
echo "SYNOPSIS"
10-
echo " kubectl appurl <Kind> <Instance> <Namespace> -k <Absolute path to kubeconfig>"
10+
echo " kubectl appurl <Kind> <Instance> -k <Absolute path to kubeconfig>"
1111
echo ""
1212
echo "DESCRIPTION"
13-
echo " kubectl appurl shows application url. For this it searches for Service object with type NodePort or LoadBalancer in "
14-
echo " the connectivity graph of the input <Kind> <Instance> pair and builds the application url. "
13+
echo " kubectl appurl parses the application url. It first looks for Ingress objects; if no Ingress is defined then it looks for Service objects."
1514
exit 0
1615
}
1716

18-
if (( $# < 5 )); then
17+
if (( $# < 4 )); then
1918
print_help
2019
fi
2120

2221
kind=$1
2322
instance=$2
24-
namespace=$3
2523
kubeconfig1=""
2624

27-
shift;
2825
shift;
2926
shift;
3027

@@ -41,36 +38,19 @@ done
4138

4239
kubeconfig="--kubeconfig="$kubeconfig1
4340

44-
#if [ $# = 4 ] && [[ $4 == *"kubeconfig="* ]]; then
45-
# kubeconfig=$4
46-
#fi
47-
48-
check_namespace $namespace $kubeconfig
49-
5041
canonicalKind=$(get_canonical_kind $kind)
5142

5243
if [[ $canonicalKind == *"Unknown"* ]]; then
5344
echo "$canonicalKind"
5445
exit 0
5546
fi
5647

57-
resStatus=`kubectl $kubeconfig get $kind $instance -n $namespace -o json 2>&1`
48+
kubeplusNamespace=`kubectl get pods -A $kubeconfig | grep kubeplus-deployment | awk '{print $1}'`
49+
resStatus=`kubectl $kubeconfig get $kind $instance -n $kubeplusNamespace -o json 2>&1`
5850
if [[ $resStatus =~ 'Error' ]]; then
59-
# echo "Instance $instance of $kind not deployed properly."
6051
echo $resStatus
6152
exit 0
6253
fi
6354

6455

65-
python3 /$KUBEPLUS_HOME/plugins/appurl.py connections $canonicalKind $instance $namespace $kubeconfig
66-
67-
#appPort=`python /$KUBEPLUS_HOME/plugins/appurl.py connections $kind $instance $namespace $kubeconfig`
68-
69-
#context=`kubectl config current-context $kubeconfig`
70-
#if [[ $context == 'minikube' ]]; then
71-
# appIP=`minikube ip`
72-
# appURL="http://$appIP:$appPort"
73-
# echo "$appURL"
74-
#fi
75-
76-
56+
python3 /$KUBEPLUS_HOME/plugins/appurl.py $canonicalKind $instance $kubeconfig

0 commit comments

Comments
 (0)