├── .gitignore ├── LICENSE ├── README.md ├── cli.json ├── cmd ├── connectivity_problems.go ├── constants.go ├── content_problems.go ├── curl.go ├── dig.go ├── edge_locations.go ├── estats.go ├── grep.go ├── gtm_hostnames.go ├── ipa_hostnames.go ├── locate_ip.go ├── mtr.go ├── root.go ├── translate_error.go ├── translate_url.go ├── types.go ├── types_json.go ├── url_health_check.go ├── user_diagnostics.go ├── user_diagnostics_create.go ├── user_diagnostics_get.go ├── user_diagnostics_list.go ├── verify_ip.go └── verify_locate_ip.go ├── go.mod ├── go.sum ├── internal ├── api_client.go ├── cobra_customisation.go ├── constants.go ├── edge_grid_http_client.go ├── en_US.json ├── errors.go ├── logging.go ├── message_utils.go ├── print_diagnostics_util.go ├── print_utils.go ├── service.go ├── template.go ├── types.go ├── utils.go └── validator.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2021 Akamai Technologies, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "requirements" : { 3 | "go": ">=1.17" 4 | }, 5 | "commands" : [ 6 | { 7 | "name":"diagnostics", 8 | "version":"v1.1.0", 9 | "aliases": ["diag", "edge-diagnostics"], 10 | "description":"Edge Diagnostics enables you to identify, analyze, and troubleshoot common content delivery network issues that your users may encounter.", 11 | "bin": "https://github.com/akamai/cli-diagnostics/releases/download/{{.Version}}/akamai-{{.Name}}-{{.Version}}-{{.OS}}{{.Arch}}{{.BinSuffix}}", 12 | "auto-complete": true 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /cmd/connectivity_problems.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var connectivityProblemsRequest internal.ConnectivityProblemsRequest 9 | var portStr string 10 | 11 | var connectivityProblemsCmd = &cobra.Command{ 12 | Use: connectivityProblemsUse, 13 | Example: connectivityProblemsExample, 14 | Aliases: []string{"cvp"}, 15 | Args: cobra.MaximumNArgs(1), 16 | Run: func(cmd *cobra.Command, args []string) { 17 | 18 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 19 | api := internal.NewApiClient(*eghc) 20 | svc := internal.NewService(*api, cmd, globalFlags.json) 21 | validator := internal.NewValidator(cmd, jsonData) 22 | 23 | validator.ValidateConnectivityProblemsFields(args, portStr, &connectivityProblemsRequest) 24 | svc.ConnectivityProblems(connectivityProblemsRequest) 25 | }, 26 | } 27 | 28 | func init() { 29 | rootCmd.AddCommand(connectivityProblemsCmd) 30 | translateErrorCmd.Flags().SortFlags = false 31 | 32 | connectivityProblemsCmd.Flags().StringVarP(&connectivityProblemsRequest.EdgeLocationId, "client-location", "l", "", internal.GetMessageForKey(connectivityProblemsCmd, "client-location")) 33 | connectivityProblemsCmd.Flags().StringVarP(&connectivityProblemsRequest.SpoofEdgeIp, "edge-server-ip", "e", "", internal.GetMessageForKey(connectivityProblemsCmd, "edge-server-ip")) 34 | connectivityProblemsCmd.Flags().StringVar(&connectivityProblemsRequest.ClientIp, "client-ip", "", internal.GetMessageForKey(connectivityProblemsCmd, "clientIp")) 35 | connectivityProblemsCmd.Flags().StringArrayVarP(&connectivityProblemsRequest.RequestHeaders, "request-header", "H", []string{}, internal.GetMessageForKey(connectivityProblemsCmd, "requestHeader")) 36 | connectivityProblemsCmd.Flags().StringVarP(&connectivityProblemsRequest.IpVersion, "ip-version", "i", "", internal.GetMessageForKey(connectivityProblemsCmd, "ipVersion")) 37 | connectivityProblemsCmd.Flags().StringVar(&connectivityProblemsRequest.PacketType, "packet-type", "", internal.GetMessageForKey(connectivityProblemsCmd, "packetType")) 38 | connectivityProblemsCmd.Flags().StringVarP(&portStr, "port", "p", "", internal.GetMessageForKey(connectivityProblemsCmd, "port")) 39 | connectivityProblemsCmd.Flags().BoolVarP(&connectivityProblemsRequest.RunFromSiteShield, "run-from-site-shield-map", "r", false, internal.GetMessageForKey(connectivityProblemsCmd, "run-from-site-shield-map")) 40 | 41 | connectivityProblemsCmd.Short = internal.GetMessageForKey(connectivityProblemsCmd, internal.Short) 42 | connectivityProblemsCmd.Long = internal.GetMessageForKey(connectivityProblemsCmd, internal.Long) 43 | } 44 | -------------------------------------------------------------------------------- /cmd/constants.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | //command usage and example 4 | const ( 5 | locateIpUse = "locate-ip IP_ADDRESS..." 6 | locateIpExample = ` $ akamai diagnostics locate-ip 123.123.123.123 8.8.8.8` 7 | 8 | verifyIpUse = "verify-ip IP_ADDRESS..." 9 | verifyIpExample = ` $ akamai diagnostics verify-ip 123.123.123.123 8.8.8.8` 10 | 11 | verifyLocateIpUse = "verify-locate-ip IP_ADDRESS" 12 | verifyLocateIpExample = ` $ akamai diagnostics verify-locate-ip 123.123.123.123` 13 | 14 | digUse = "dig --hostname HOSTNAME [-l CLIENT_LOCATION | -e EDGE_SERVER_IP] [-q QUERY_TYPE] [--gtm]" 15 | digExample = ` $ akamai diagnostics dig --hostname www.example.com --client-location bangalore-india --query-type NS --gtm 16 | $ akamai diagnostics dig --hostname www.easybrazilinvesting.com --edge-server-ip 123.123.123.123 --query-type A 17 | $ akamai diagnostics dig --hostname www.easybrazilinvesting.com` 18 | 19 | userDiagnosticsUse = "user-diagnostics" 20 | 21 | userDiagnosticsCreateUse = "create {--url URL | --ipa-hostname IPA_HOSTNAME} [--notes NOTE]" 22 | userDiagnosticsCreateExample = ` $ akamai diagnostics user-diagnostics create --url https://www.akamai.com 23 | $ akamai diagnostics user-diagnostics create --url https://www.akamai.com --notes "Tokyo olympics" 24 | $ akamai diagnostics user-diagnostics create --ipa-hostname https://www.akamai.com --notes "Tokyo olympics"` 25 | 26 | userDiagnosticsListUse = "list [--url URL|IPA_HOSTNAME] [--user USER] [--active]" 27 | userDiagnosticsListExample = ` $ akamai diagnostics user-diagnostics list 28 | $ akamai diagnostics user-diagnostics list --url https://www.akamai.com --user johnDoe --active 29 | $ akamai diagnostics user-diagnostics list --url https://www.akamai.com --active` 30 | 31 | userDiagnosticsGetUse = "get LINK_ID [--mtr] [--dig] [--curl]" 32 | userDiagnosticsGetExample = ` $ akamai diagnostics user-diagnostics get ab123c 33 | $ akamai diagnostics user-diagnostics get ab123c --mtr --curl 34 | $ akamai diagnostics user-diagnostics get ab123c --dig --mtr` 35 | 36 | translateUrlUse = "translate-url URL" 37 | translateUrlExample = ` $ akamai diagnostics translate-url http://www.example.com` 38 | 39 | translateErrorUse = "translate-error-string ERROR_STRING [--trace-forward-logs]" 40 | translateErrorExample = ` $ akamai diagnostics translate-error-string 9.6f64d440.1318965461.2f2b078 --trace-forward-logs` 41 | 42 | urlHealthCheckUse = "url-health-check URL [--client-location LOCATION] [--edge-server-ip EDGE_SERVER_IP] [--ip-version IPv4|IPv6] [--port 80|443] [--packet-type TCP|ICMP] [--request-header REQUEST_HEADER...] [-q QUERY_TYPE] [--run-from-site-shield-map] [--logs] [--network-connectivity]" 43 | urlHealthCheckExample = ` $ akamai diagnostics url-health-check http://www.example.com --run-from-site-shield-map 44 | $ akamai diagnostics url-health-check http://www.example.com --client-location bangalore-india --edge-server-ip 123.123.123.123 --port 80 --packet-type TCP --ip-version IPV4 --logs --network-connectivity --request-header "X-Location: NGDT"` 45 | 46 | edgeLocationsUse = "edge-locations [--search REGION]" 47 | edgeLocationsExample = ` $ akamai diagnostics edge-locations 48 | $ akamai diagnostics edge-locations --search india` 49 | 50 | ipaHostnamesUse = "ipa-hostnames" 51 | ipaHostnamesExample = `$ akamai diagnostics ipa-hostnames` 52 | 53 | gtmHostnamesUse = "gtm-hostnames [--test-target-ip GTM_HOSTNAME]" 54 | gtmHostnamesExample = ` $ akamai diagnostics gtm-hostnames 55 | $ akamai diagnostics gtm-hostnames --test-target-ip www-origin.20000puzzles.akadns.net` 56 | 57 | curlUse = "curl URL [--client-location CLIENT_LOCATION | --edge-server-ip EDGE_SERVER_IP] [--ip-version IPv4|IPv6] [--request-header REQUEST_HEADER...] [--run-from-site-shield-map]" 58 | curlExample = ` $ akamai diagnostics curl http://www.example.com --client-location bangalore-india --ip-version IPv4 --request-header "accept:text/html" 59 | $ akamai diagnostics curl http://www.example.com 60 | $ akamai diagnostics curl http://www.example.com -l bangalore-india -i IPv4 -H "accept:text/html" 61 | $ akamai diagnostics curl http://www.example.com -e 56.73.66.33 --run-from-site-shield-map` 62 | 63 | mtrUse = "mtr --source SOURCE_IP|SOURCE_LOCATION --destination DESTINATION_IP|HOSTNAME [--gtm-hostname GTM_HOSTNAME] [--ip-version IPv4|IPv6] [--port 80|443] [--packet-type TCP|ICMP] [--site-shield-hostname HOSTNAME]" 64 | mtrExample = ` $ akamai diagnostics mtr --source bangalore-india --destination www.example.com --ip-version IPv4 --port 443 --packet-type icmp 65 | $ akamai diagnostics mtr --source bangalore-india --destination 121.121.121.121 66 | $ akamai diagnostics mtr --source 123.123.123.123 --destination 121.121.121.121 67 | $ akamai diagnostics mtr --source 1.1.1.1 --destination 2.2.2.2 --gtm-hostname example.com 68 | $ akamai diagnostics mtr --source 123.123.123.123 --destination 121.121.121.121 --site-shield-hostname www.example1.com` 69 | 70 | grepUse = "grep EDGE_IP START_TIME END_TIME {--hostname HOSTNAME ... | --cp-code CP_CODE ...} [--client-ip CLIENT_IP ...] [--user-agent USER_AGENT ...] [--http-status-code HTTP_STATUS_CODE ... | --error-status-codes] [--arl ARL ...] [-r] [-f]" 71 | grepExample = ` $ akamai diagnostics grep 123.123.123.123 "2021-01-01T01:00:00.000Z" "2021-01-01T01:30:00.000Z" --hostname "www.akamai.com" --client-ip "123.123.123.123" --http-status-code "400, 401" -rf 72 | $ akamai diagnostics grep 123.123.123.123 "2021-01-01T01:00:00.000Z" "2021-01-01T01:30:00.000Z" --cp-code 12345 --client-ip "123.123.123.123" --http-status-code "400, 401" -rf` 73 | 74 | estatsUse = "estats {--url URL | --cp-code CP_CODE} [--logs] [--enhanced-tls | --standard-tls] [--edge-errors] [--origin-errors]" 75 | estatsExample = ` $ akamai diagnostics estats --url https://www.example.com 76 | $ akamai diagnostics estats --cp-code 12345 --enhanced-tls --edge-errors 77 | $ akamai diagnostics estats --url https://www.example.com --logs 78 | $ akamai diagnostics estats --cp-code 12345 --logs --standard-tls` 79 | 80 | connectivityProblemsUse = "connectivity-problem URL [--client-location LOCATION] [--edge-server-ip EDGE_SERVER_IP] [--client-ip CLIENT_IP] [--request-header REQUEST_HEADER...] [--ip-version IPv4|IPv6] [--port 80|443] [--packet-type TCP|ICMP] [--run-from-site-shield-map]" 81 | connectivityProblemsExample = ` $ akamai diagnostics connectivity-problem http://www.example.com --client-location bangalore-india 82 | $ akamai diagnostics connectivity-problem http://www.example.com --run-from-site-shield-map 83 | $ akamai diagnostics connectivity-problem http://www.example.com --client-location bangalore-india --edge-server-ip 123.123.123.123 --client-ip 123.123.123.123 --request-header accept:text/html --port 80 --packet-type TCP --ip-version IPV4` 84 | 85 | contentProblemsUse = "content-problem URL [--client-location LOCATION] [--edge-server-ip EDGE_IP] [--request-header REQUEST_HEADER...] [--ip-version IP_VERSION] [--run-from-site-shield-map]" 86 | contentProblemsExample = ` $ akamai diagnostics content-problem http://www.example.com 87 | $ akamai diagnostics content-problem http://www.example.com --client-location bangalore-india --run-from-site-shield-map 88 | $ akamai diagnostics content-problem http://www.example.com --client-location bangalore-india --edge-server-ip 123.123.123.123 --request-header accept:text/html --ip-version IPV4` 89 | ) 90 | -------------------------------------------------------------------------------- /cmd/content_problems.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var ( 9 | contentProblemsRequest internal.ContentProblemsRequest 10 | ) 11 | 12 | var contentProblemsCmd = &cobra.Command{ 13 | Use: contentProblemsUse, 14 | Example: contentProblemsExample, 15 | Aliases: []string{"cp"}, 16 | Args: cobra.MaximumNArgs(1), 17 | Run: func(cmd *cobra.Command, args []string) { 18 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 19 | api := internal.NewApiClient(*eghc) 20 | svc := internal.NewService(*api, cmd, globalFlags.json) 21 | validator := internal.NewValidator(cmd, jsonData) 22 | 23 | validator.ValidateContentProblemsFields(args, &contentProblemsRequest) 24 | 25 | svc.ContentProblems(contentProblemsRequest) 26 | }, 27 | } 28 | 29 | func init() { 30 | 31 | rootCmd.AddCommand(contentProblemsCmd) 32 | contentProblemsCmd.Flags().SortFlags = false 33 | 34 | contentProblemsCmd.Short = internal.GetMessageForKey(contentProblemsCmd, internal.Short) 35 | contentProblemsCmd.Long = internal.GetMessageForKey(contentProblemsCmd, internal.Long) 36 | 37 | contentProblemsCmd.Flags().StringVarP(&contentProblemsRequest.EdgeLocationId, "client-location", "l", "", internal.GetMessageForKey(contentProblemsCmd, "client-location")) 38 | contentProblemsCmd.Flags().StringVarP(&contentProblemsRequest.EdgeIp, "edge-server-ip", "e", "", internal.GetMessageForKey(contentProblemsCmd, "edge-server-ip")) 39 | contentProblemsCmd.Flags().StringVarP(&contentProblemsRequest.IpVersion, "ip-version", "i", "", internal.GetMessageForKey(contentProblemsCmd, "ip-version")) 40 | contentProblemsCmd.Flags().StringArrayVarP(&contentProblemsRequest.RequestHeaders, "request-header", "H", []string{}, internal.GetMessageForKey(contentProblemsCmd, "requestHeader")) 41 | contentProblemsCmd.Flags().BoolVarP(&contentProblemsRequest.RunFromSiteShield, "run-from-site-shield-map", "r", false, internal.GetMessageForKey(contentProblemsCmd, "run-from-site-shield-map")) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /cmd/curl.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var curlRequest internal.CurlRequest 9 | 10 | var curlCmd = &cobra.Command{ 11 | Use: curlUse, 12 | Example: curlExample, 13 | Args: cobra.MaximumNArgs(1), 14 | Run: func(cmd *cobra.Command, args []string) { 15 | 16 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 17 | api := internal.NewApiClient(*eghc) 18 | svc := internal.NewService(*api, cmd, globalFlags.json) 19 | validator := internal.NewValidator(cmd, jsonData) 20 | 21 | validator.ValidateCurlFields(args, &curlRequest) 22 | svc.Curl(curlRequest) 23 | 24 | }, 25 | } 26 | 27 | func init() { 28 | 29 | rootCmd.AddCommand(curlCmd) 30 | curlCmd.Flags().SortFlags = false 31 | 32 | curlCmd.Short = internal.GetMessageForKey(curlCmd, internal.Short) 33 | curlCmd.Long = internal.GetMessageForKey(curlCmd, internal.Long) 34 | 35 | curlCmd.Flags().StringVarP(&curlRequest.EdgeLocationId, "client-location", "l", "", internal.GetMessageForKey(curlCmd, "clientLocation")) 36 | curlCmd.Flags().StringVarP(&curlRequest.EdgeIp, "edge-server-ip", "e", "", internal.GetMessageForKey(curlCmd, "edgeServerIp")) 37 | curlCmd.Flags().StringVarP(&curlRequest.IpVersion, "ip-version", "i", internal.IPV4, internal.GetMessageForKey(curlCmd, "ipVersion")) 38 | curlCmd.Flags().StringArrayVarP(&curlRequest.RequestHeaders, "request-header", "H", []string{}, internal.GetMessageForKey(curlCmd, "requestHeader")) 39 | curlCmd.Flags().BoolVarP(&curlRequest.RunFromSiteShield, "run-from-site-shield-map", "r", false, internal.GetMessageForKey(curlCmd, "run-from-site-shield-map")) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /cmd/dig.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var digRequest internal.DigRequest 9 | 10 | var digCmd = &cobra.Command{ 11 | Use: digUse, 12 | Example: digExample, 13 | Args: cobra.ExactArgs(0), 14 | Run: func(cmd *cobra.Command, args []string) { 15 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 16 | api := internal.NewApiClient(*eghc) 17 | svc := internal.NewService(*api, cmd, globalFlags.json) 18 | validator := internal.NewValidator(cmd, jsonData) 19 | 20 | validator.ValidateDigFields(args, &digRequest) 21 | svc.Dig(digRequest) 22 | }, 23 | } 24 | 25 | func init() { 26 | 27 | rootCmd.AddCommand(digCmd) 28 | digCmd.Flags().SortFlags = false 29 | 30 | digCmd.Short = internal.GetMessageForKey(digCmd, internal.Short) 31 | digCmd.Long = internal.GetMessageForKey(digCmd, internal.Long) 32 | 33 | digCmd.Flags().StringVarP(&digRequest.Hostname, "hostname", "d", "", internal.GetMessageForKey(digCmd, "hostname")) 34 | digCmd.Flags().StringVarP(&digRequest.ClientLocation, "client-location", "l", "", internal.GetMessageForKey(digCmd, "client-location")) 35 | digCmd.Flags().StringVarP(&digRequest.EdgeServerIp, "edge-server-ip", "e", "", internal.GetMessageForKey(digCmd, "edge-server-ip")) 36 | digCmd.Flags().StringVarP(&digRequest.QueryType, "query-type", "q", "A", internal.GetMessageForKey(digCmd, "query-type")) 37 | digCmd.Flags().BoolVarP(&digRequest.IsGtmHostName, "gtm", "g", false, internal.GetMessageForKey(digCmd, "gtm")) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /cmd/edge_locations.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/akamai/cli-diagnostics/internal" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var ( 11 | searchText string 12 | ) 13 | 14 | var edgeLocationsCmd = &cobra.Command{ 15 | Use: edgeLocationsUse, 16 | Example: edgeLocationsExample, 17 | Aliases: []string{"el"}, 18 | Args: cobra.ExactArgs(0), 19 | Run: func(cmd *cobra.Command, args []string) { 20 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 21 | api := internal.NewApiClient(*eghc) 22 | svc := internal.NewService(*api, cmd, globalFlags.json) 23 | 24 | searchText = strings.ToLower(searchText) 25 | svc.EdgeLocations(searchText) 26 | }, 27 | } 28 | 29 | func init() { 30 | 31 | rootCmd.AddCommand(edgeLocationsCmd) 32 | edgeLocationsCmd.Flags().SortFlags = false 33 | edgeLocationsCmd.Flags().StringVar(&searchText, "search", "", internal.GetMessageForKey(edgeLocationsCmd, internal.Search)) 34 | 35 | edgeLocationsCmd.Short = internal.GetMessageForKey(edgeLocationsCmd, internal.Short) 36 | edgeLocationsCmd.Long = internal.GetMessageForKey(edgeLocationsCmd, internal.Long) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /cmd/estats.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var logsEstatsFlag bool 9 | var enhancedTlsEstatsFlag bool 10 | var standardTlsEstatsFlag bool 11 | var edgeErrorsEstatsFlag bool 12 | var originErrorsEstatsFlag bool 13 | 14 | var estatsRequest internal.EstatsRequest 15 | 16 | var errorStatsCmd = &cobra.Command{ 17 | Use: estatsUse, 18 | Example: estatsExample, 19 | Args: cobra.ExactArgs(0), 20 | Run: func(cmd *cobra.Command, args []string) { 21 | 22 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 23 | api := internal.NewApiClient(*eghc) 24 | svc := internal.NewService(*api, cmd, globalFlags.json) 25 | validator := internal.NewValidator(cmd, jsonData) 26 | 27 | validator.ValidateEstatsFields(args, &estatsRequest, logsEstatsFlag, enhancedTlsEstatsFlag, standardTlsEstatsFlag, edgeErrorsEstatsFlag, originErrorsEstatsFlag) 28 | svc.Estats(estatsRequest, logsEstatsFlag) 29 | 30 | }, 31 | } 32 | 33 | func init() { 34 | rootCmd.AddCommand(errorStatsCmd) 35 | 36 | errorStatsCmd.Flags().SortFlags = false 37 | 38 | errorStatsCmd.Short = internal.GetMessageForKey(errorStatsCmd, internal.Short) 39 | errorStatsCmd.Long = internal.GetMessageForKey(errorStatsCmd, internal.Long) 40 | 41 | errorStatsCmd.Flags().StringVarP(&estatsRequest.Url, "url", "u", "", internal.GetMessageForKey(errorStatsCmd, "url")) 42 | errorStatsCmd.Flags().IntVarP(&estatsRequest.CpCode, "cp-code", "c", 0, internal.GetMessageForKey(errorStatsCmd, "cpCode")) 43 | errorStatsCmd.Flags().BoolVar(&logsEstatsFlag, "logs", false, internal.GetMessageForKey(errorStatsCmd, "logs")) 44 | errorStatsCmd.Flags().BoolVar(&enhancedTlsEstatsFlag, "enhanced-tls", false, internal.GetMessageForKey(errorStatsCmd, "enhancedTls")) 45 | errorStatsCmd.Flags().BoolVar(&standardTlsEstatsFlag, "standard-tls", false, internal.GetMessageForKey(errorStatsCmd, "standardTls")) 46 | errorStatsCmd.Flags().BoolVarP(&edgeErrorsEstatsFlag, "edge-errors", "e", false, internal.GetMessageForKey(errorStatsCmd, "edgeErrors")) 47 | errorStatsCmd.Flags().BoolVarP(&originErrorsEstatsFlag, "origin-errors", "o", false, internal.GetMessageForKey(errorStatsCmd, "originErrors")) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /cmd/grep.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var httpStatusCode []string 9 | var arls []string 10 | var errorStatusCodeFlag bool 11 | var clientRequestFlag bool 12 | var forwardRequestFlag bool 13 | 14 | var grepRequest internal.GrepRequest 15 | 16 | var grepCmd = &cobra.Command{ 17 | Use: grepUse, 18 | Example: grepExample, 19 | Args: cobra.MaximumNArgs(3), 20 | Run: func(cmd *cobra.Command, args []string) { 21 | 22 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 23 | api := internal.NewApiClient(*eghc) 24 | svc := internal.NewService(*api, cmd, globalFlags.json) 25 | validator := internal.NewValidator(cmd, jsonData) 26 | 27 | validator.ValidateGrepFields(args, &grepRequest, errorStatusCodeFlag, clientRequestFlag, forwardRequestFlag, httpStatusCode, arls) 28 | svc.Grep(grepRequest) 29 | 30 | }, 31 | } 32 | 33 | func init() { 34 | 35 | rootCmd.AddCommand(grepCmd) 36 | grepCmd.Short = internal.GetMessageForKey(grepCmd, internal.Short) 37 | grepCmd.Long = internal.GetMessageForKey(grepCmd, internal.Long) 38 | 39 | grepCmd.Flags().StringSliceVarP(&grepRequest.Hostnames, "hostname", "d", nil, internal.GetMessageForKey(grepCmd, "hostname")) 40 | grepCmd.Flags().IntSliceVarP(&grepRequest.CpCodes, "cp-code", "c", nil, internal.GetMessageForKey(grepCmd, "cpCode")) 41 | grepCmd.Flags().StringSliceVar(&grepRequest.ClientIps, "client-ip", nil, internal.GetMessageForKey(grepCmd, "clientIp")) 42 | grepCmd.Flags().StringSliceVar(&grepRequest.UserAgents, "user-agent", nil, internal.GetMessageForKey(grepCmd, "userAgent")) 43 | grepCmd.Flags().StringSliceVar(&httpStatusCode, "http-status-code", nil, internal.GetMessageForKey(grepCmd, "httpStatusCode")) 44 | grepCmd.Flags().BoolVar(&errorStatusCodeFlag, "error-status-codes", false, internal.GetMessageForKey(grepCmd, "errorStatusCodes")) 45 | grepCmd.Flags().StringSliceVarP(&arls, "arl", "a", nil, internal.GetMessageForKey(grepCmd, "arl")) 46 | grepCmd.Flags().BoolVarP(&clientRequestFlag, "r", "r", true, internal.GetMessageForKey(grepCmd, "r")) 47 | grepCmd.Flags().BoolVarP(&forwardRequestFlag, "f", "f", false, internal.GetMessageForKey(grepCmd, "f")) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /cmd/gtm_hostnames.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var ( 9 | testTargetIp string 10 | ) 11 | 12 | var gtmHostnamesCmd = &cobra.Command{ 13 | Use: gtmHostnamesUse, 14 | Example: gtmHostnamesExample, 15 | Aliases: []string{"gtm"}, 16 | Args: cobra.ExactArgs(0), 17 | 18 | Run: func(cmd *cobra.Command, args []string) { 19 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 20 | api := internal.NewApiClient(*eghc) 21 | svc := internal.NewService(*api, cmd, globalFlags.json) 22 | 23 | if testTargetIp != "" { 24 | svc.GtmTestTargetIp(testTargetIp) 25 | } else { 26 | svc.GtmHostnames() 27 | } 28 | }, 29 | } 30 | 31 | func init() { 32 | 33 | rootCmd.AddCommand(gtmHostnamesCmd) 34 | gtmHostnamesCmd.Flags().SortFlags = false 35 | gtmHostnamesCmd.Flags().StringVarP(&testTargetIp, "test-target-ip", "t", "", internal.GetMessageForKey(gtmHostnamesCmd, internal.TestTargetIp)) 36 | 37 | gtmHostnamesCmd.Short = internal.GetMessageForKey(gtmHostnamesCmd, internal.Short) 38 | gtmHostnamesCmd.Long = internal.GetMessageForKey(gtmHostnamesCmd, internal.Long) 39 | 40 | } 41 | -------------------------------------------------------------------------------- /cmd/ipa_hostnames.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var ipaHostnamesCmd = &cobra.Command{ 9 | Use: ipaHostnamesUse, 10 | Example: ipaHostnamesExample, 11 | Aliases: []string{"ipa"}, 12 | Args: cobra.ExactArgs(0), 13 | 14 | Run: func(cmd *cobra.Command, args []string) { 15 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 16 | api := internal.NewApiClient(*eghc) 17 | svc := internal.NewService(*api, cmd, globalFlags.json) 18 | 19 | svc.IpaHostnames() 20 | }, 21 | } 22 | 23 | func init() { 24 | 25 | rootCmd.AddCommand(ipaHostnamesCmd) 26 | ipaHostnamesCmd.Flags().SortFlags = false 27 | 28 | ipaHostnamesCmd.Short = internal.GetMessageForKey(ipaHostnamesCmd, internal.Short) 29 | ipaHostnamesCmd.Long = internal.GetMessageForKey(ipaHostnamesCmd, internal.Long) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /cmd/locate_ip.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var locateIpsRequest internal.VerifyLocateIpsRequest 9 | 10 | var locateIpCmd = &cobra.Command{ 11 | Use: locateIpUse, 12 | Example: locateIpExample, 13 | Aliases: []string{"li"}, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | 16 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 17 | api := internal.NewApiClient(*eghc) 18 | svc := internal.NewService(*api, cmd, globalFlags.json) 19 | validator := internal.NewValidator(cmd, jsonData) 20 | 21 | validator.ValidateVerifyIpOrLocateIpFields(args, &locateIpsRequest) 22 | svc.LocateIp(locateIpsRequest) 23 | 24 | }, 25 | } 26 | 27 | func init() { 28 | 29 | rootCmd.AddCommand(locateIpCmd) 30 | locateIpCmd.Flags().SortFlags = false 31 | 32 | locateIpCmd.Short = internal.GetMessageForKey(locateIpCmd, internal.Short) 33 | locateIpCmd.Long = internal.GetMessageForKey(locateIpCmd, internal.Long) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/mtr.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var mtrRequest internal.MtrRequest 9 | 10 | var mtrCmd = &cobra.Command{ 11 | Use: mtrUse, 12 | Example: mtrExample, 13 | Args: cobra.ExactArgs(0), 14 | Run: func(cmd *cobra.Command, args []string) { 15 | 16 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 17 | api := internal.NewApiClient(*eghc) 18 | svc := internal.NewService(*api, cmd, globalFlags.json) 19 | validator := internal.NewValidator(cmd, jsonData) 20 | 21 | validator.ValidateMtrFields(args, portStr, &mtrRequest) 22 | svc.Mtr(mtrRequest) 23 | 24 | }, 25 | } 26 | 27 | func init() { 28 | 29 | rootCmd.AddCommand(mtrCmd) 30 | mtrCmd.Flags().SortFlags = false 31 | 32 | mtrCmd.Short = internal.GetMessageForKey(mtrCmd, internal.Short) 33 | mtrCmd.Long = internal.GetMessageForKey(mtrCmd, internal.Long) 34 | 35 | mtrCmd.Flags().StringVarP(&mtrRequest.Source, "source", "s", "", internal.GetMessageForKey(mtrCmd, "source")) 36 | mtrCmd.Flags().StringVarP(&mtrRequest.Destination, "destination", "d", "", internal.GetMessageForKey(mtrCmd, "destination")) 37 | mtrCmd.Flags().StringVarP(&mtrRequest.GtmHostname, "gtm-hostname", "g", "", internal.GetMessageForKey(mtrCmd, "gtm-hostname")) 38 | mtrCmd.Flags().StringVar(&mtrRequest.PacketType, "packet-type", internal.TCP, internal.GetMessageForKey(mtrCmd, "packet-type")) 39 | mtrCmd.Flags().StringVarP(&portStr, "port", "p", "", internal.GetMessageForKey(mtrCmd, "port")) 40 | mtrCmd.Flags().StringVarP(&mtrRequest.IPVersion, "ip-version", "i", "", internal.GetMessageForKey(mtrCmd, "ip-version")) 41 | mtrCmd.Flags().StringVarP(&mtrRequest.SiteShieldHostname, "site-shield-hostname", "t", "", internal.GetMessageForKey(mtrCmd, "site-shield-hostname")) 42 | // few flags are not set to default value because they're redundant in few scenarios 43 | } 44 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/fatih/color" 6 | homedir "github.com/mitchellh/go-homedir" 7 | "github.com/spf13/cobra" 8 | "os" 9 | "path/filepath" 10 | "runtime" 11 | "strconv" 12 | ) 13 | 14 | var jsonData []byte 15 | 16 | type GlobalFlags struct { 17 | edgeRcPath string 18 | edgeRcSection string 19 | forceColor bool 20 | accountSwitchKey string 21 | json bool 22 | } 23 | 24 | var globalFlags GlobalFlags 25 | 26 | // rootCmd represents the base command when called without any subcommands 27 | var rootCmd = &cobra.Command{ 28 | Use: "diagnostics", 29 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 30 | if globalFlags.forceColor { 31 | color.NoColor = false 32 | } 33 | 34 | if runtime.GOOS == "windows" { 35 | color.NoColor = true 36 | } 37 | }, 38 | } 39 | 40 | // Execute adds all child commands to the root command and sets flags appropriately. 41 | // This is called by main.main(). It only needs to happen once to the rootCmd. 42 | func Execute(Version string) { 43 | rootCmd.Version = Version 44 | if err := rootCmd.Execute(); err != nil { 45 | os.Exit(internal.CliErrExitCode) 46 | } 47 | } 48 | 49 | func init() { 50 | rootCmd.CompletionOptions.DisableDefaultCmd = true // Remove this if we choose to offer a completion command 51 | 52 | jsonData = internal.ReadStdin() 53 | 54 | rootCmd.Flags().SortFlags = false 55 | rootCmd.PersistentFlags().SortFlags = false 56 | 57 | rootCmd.Short = internal.GetMessageForKey(rootCmd, internal.Short) 58 | rootCmd.Long = internal.GetMessageForKey(rootCmd, internal.Long) 59 | 60 | defaultEdgercPath := os.Getenv("AKAMAI_EDGERC") 61 | defaultEdgercSection := os.Getenv("AKAMAI_EDGERC_SECTION") 62 | jsonOutput, _ := strconv.ParseBool(os.Getenv("AKAMAI_OUTPUT_JSON")) 63 | 64 | if defaultEdgercPath == "" { 65 | if home, err := homedir.Dir(); err == nil { 66 | defaultEdgercPath = filepath.Join(home, ".edgerc") 67 | } 68 | } 69 | 70 | if defaultEdgercSection == "" { 71 | defaultEdgercSection = "diagnostics" 72 | } 73 | 74 | rootCmd.PersistentFlags().StringVar(&globalFlags.edgeRcPath, "edgerc", defaultEdgercPath, internal.GetMessageForKey(rootCmd, "edgerc")) 75 | rootCmd.PersistentFlags().StringVar(&globalFlags.edgeRcSection, "section", defaultEdgercSection, internal.GetMessageForKey(rootCmd, "section")) 76 | rootCmd.PersistentFlags().StringVar(&globalFlags.accountSwitchKey, "account-key", "", internal.GetMessageForKey(rootCmd, "account-key")) 77 | rootCmd.PersistentFlags().BoolVar(&globalFlags.forceColor, "force-color", false, internal.GetMessageForKey(rootCmd, "force-color")) 78 | rootCmd.PersistentFlags().BoolVar(&globalFlags.json, "json", jsonOutput, internal.GetMessageForKey(rootCmd, "json")) 79 | 80 | rootCmd.SetUsageTemplate(internal.CustomUsageTemplate) 81 | cobra.AddTemplateFuncs(internal.UsageFuncMap) 82 | 83 | } 84 | -------------------------------------------------------------------------------- /cmd/translate_error.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var traceForwardLogs bool 9 | var errorTranslatorRequest internal.ErrorTranslatorRequest 10 | 11 | var translateErrorCmd = &cobra.Command{ 12 | Use: translateErrorUse, 13 | Example: translateErrorExample, 14 | Aliases: []string{"tes"}, 15 | Args: cobra.MaximumNArgs(1), 16 | Run: func(cmd *cobra.Command, args []string) { 17 | 18 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 19 | api := internal.NewApiClient(*eghc) 20 | svc := internal.NewService(*api, cmd, globalFlags.json) 21 | validator := internal.NewValidator(cmd, jsonData) 22 | 23 | validator.ValidateTranslateErrorFields(args, &errorTranslatorRequest, traceForwardLogs) 24 | svc.TranslateError(errorTranslatorRequest) 25 | 26 | }, 27 | } 28 | 29 | func init() { 30 | rootCmd.AddCommand(translateErrorCmd) 31 | translateErrorCmd.Flags().SortFlags = false 32 | 33 | translateErrorCmd.Short = internal.GetMessageForKey(translateErrorCmd, internal.Short) 34 | translateErrorCmd.Long = internal.GetMessageForKey(translateErrorCmd, internal.Long) 35 | 36 | translateErrorCmd.Flags().BoolVarP(&traceForwardLogs, "trace-forward-logs", "t", false, internal.GetMessageForKey(translateErrorCmd, "trace-forward-logs")) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/translate_url.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var translateUrlRequest internal.ArlRequest 9 | 10 | var translateUrlCmd = &cobra.Command{ 11 | Use: translateUrlUse, 12 | Example: translateUrlExample, 13 | Aliases: []string{"tu"}, 14 | Args: cobra.MaximumNArgs(1), 15 | Run: func(cmd *cobra.Command, args []string) { 16 | 17 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 18 | api := internal.NewApiClient(*eghc) 19 | svc := internal.NewService(*api, cmd, globalFlags.json) 20 | validator := internal.NewValidator(cmd, jsonData) 21 | 22 | validator.ValidateTranslateUrlFields(args, &translateUrlRequest) 23 | svc.TranslateUrl(translateUrlRequest) 24 | 25 | }, 26 | } 27 | 28 | func init() { 29 | rootCmd.AddCommand(translateUrlCmd) 30 | translateUrlCmd.Flags().SortFlags = false 31 | 32 | translateUrlCmd.Short = internal.GetMessageForKey(translateUrlCmd, internal.Short) 33 | translateUrlCmd.Long = internal.GetMessageForKey(translateUrlCmd, internal.Long) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /cmd/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020. Akamai Technologies, Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | edgegrid "github.com/akamai/AkamaiOPEN-edgegrid-golang" 19 | ) 20 | 21 | var econfig edgegrid.Config 22 | 23 | type GeoLocation struct { 24 | AreaCode string `json:"areaCode"` 25 | AsNum string `json:"asNum"` 26 | City string `json:"city"` 27 | ClientIP string `json:"clientIp"` 28 | Continent string `json:"continent"` 29 | CountryCode string `json:"countryCode"` 30 | County string `json:"county"` 31 | DMA *int `json:"dma"` 32 | FIPS string `json:"fips"` 33 | Latitude float32 `json:"latitude"` 34 | Longitude float32 `json:"longitude"` 35 | MSA *int `json:"msa"` 36 | Network string `json:"network"` 37 | NetworkType string `json:"networkType"` 38 | PMSA *int `json:"pmsa"` 39 | Proxy string `json:"proxy"` 40 | RegionCode string `json:"regionCode"` 41 | Throughput string `json:"throughput"` 42 | TimeZone string `json:"timeZone"` 43 | ZipCode string `json:"zipCode"` 44 | } 45 | 46 | type GhostLocationsList struct { 47 | Locations []map[string]string `json:"locations"` 48 | } 49 | 50 | type DigInfo struct { 51 | HostName string `json:"hostName"` 52 | QueryType string `json:"queryType"` 53 | AnswerSection []DnsRecord `json:"answerSection"` 54 | AuthoritySection []DnsRecord `json:"authoritySection"` 55 | Result string `json:"result"` 56 | } 57 | 58 | type CurlResults struct { 59 | HttpStatusCode int `json:"httpStatusCode"` 60 | ResponseHeaders map[string]string `json:"responseHeaders"` 61 | ResponseBody string `json:"responseBody"` 62 | } 63 | 64 | type Wrapper struct { 65 | DigInfo *DigInfo `json:"digInfo"` 66 | Mtr *MtrData `json:"mtr"` 67 | GeoLocation *GeoLocation `json:"geoLocation"` 68 | TranlatedURL *TranslatedURL `json:"translatedUrl"` 69 | TranslatedError *TranslatedError `json:"translatedError"` 70 | Curl *CurlResults `json:"curlResults"` 71 | URLDebug *DebugUrl `json:"urlDebug"` 72 | Estats *Estats `json:"eStats"` 73 | LogLines *LogLines `json:"logLines"` 74 | EndUserDiagnosticLinks []EndUserDiagnosticLink `json:"endUserDiagnosticLinks"` 75 | } 76 | 77 | type Hop struct { 78 | Avg float32 `json:"avg"` 79 | Best float32 `json:"best"` 80 | Host string `json:"host"` 81 | Last float32 `json:"last"` 82 | Loss float32 `json:"loss"` 83 | Number int `json:"number"` 84 | Sent int `json:"sent"` 85 | StdDev float32 `json:"stDev"` 86 | Worst float32 `json:"worst"` 87 | } 88 | 89 | type MtrData struct { 90 | Source string `json:"source"` 91 | Destination string `json:"destination"` 92 | StartTime string `json:"startTime"` 93 | Host string `json:"host"` 94 | PacketLoss float32 `json:"packetLoss"` 95 | AvgLatency float32 `json:"avgLatency"` 96 | Analysis string `json:"analysis"` 97 | Hops []Hop `json:"hops"` 98 | Result string `json:"result"` 99 | } 100 | 101 | type TranslatedURL struct { 102 | TypeCode string `json:"typeCode"` 103 | OriginServer string `json:"originServer"` 104 | CpCode int `json:"cpCode"` 105 | SerialNumber int `json:"serialNumber"` 106 | TTL string `json:"ttl"` 107 | } 108 | 109 | type Log struct { 110 | Description string `json:"description"` 111 | Fields map[string]interface{} `json:"fields"` 112 | } 113 | 114 | type TranslatedError struct { 115 | Url string `json:"url"` 116 | HttpResponseCode int `json:"httpResponseCode"` 117 | ClientIP string `json:"clientIp"` 118 | ConnectingIP string `json:"connectingIp"` 119 | CpCode string `json:"cpCode"` 120 | EpochTime int `json:"epochTime"` 121 | Logs []Log `json:"logs"` 122 | OriginHostname string `json:"originHostname"` 123 | OriginIP string `json:"originIp"` 124 | ReasonForFailure string `json:"reasonForFailure"` 125 | RequestMethod string `json:"requestMethod"` 126 | ServerIP string `json:"serverIp"` 127 | Timestamp string `json:"timestamp"` 128 | UserAgent string `json:"userAgent"` 129 | WafDetails string `json:wafDetails` 130 | } 131 | 132 | type DebugUrl struct { 133 | DNSinformation []string `json:"dnsInformation"` 134 | HTTPResponse []map[string]string `json:"httpResponse"` 135 | Logs []Log `json:"logs"` 136 | ResponseHeaders []string `json:"responseHeaders"` 137 | } 138 | 139 | type Estats struct { 140 | CpCode int `json:"cpCode"` 141 | EdgeErrors int `json:"edgeErrors"` 142 | EdgeFailurePercentage float32 `json:"edgeFailurePercentage"` 143 | EdgeHits int `json:"edgeHits"` 144 | EdgeStatusCodeDistribution []StatusCodeDistribution `json:"edgeStatusCodeDistribution"` 145 | OriginErrors int `json:"originError"` 146 | OriginFailurePercentage float32 `json:"originFailuerPercentage"` 147 | OriginHits int `json:"originHits"` 148 | OriginStatusCodeDistribution []StatusCodeDistribution `json:"originStatusCodeDistribution"` 149 | TopEdgeIPsWithError []EdgeIPinfo `json:"topEdgeIpsWithError"` 150 | TopEdgeIPsWithSuccess []EdgeIPinfo `json:"topEdgeIpsWithSuccess"` 151 | TopEdgeIPsWithErrorFromOrigin []EdgeIPinfo `json:"topEdgeIpsWithErrorFromOrigin"` 152 | TopEdgeIPsWithSuccessFromOrigin []EdgeIPinfo `json:"topEdgeIpsWithSuccessFromOrigin"` 153 | } 154 | 155 | type StatusCodeDistribution struct { 156 | Hits int `json:"hits"` 157 | HTTPStatus int `json:"httpStatus"` 158 | Percentage float32 `json:"percentage"` 159 | } 160 | 161 | type EdgeIPinfo struct { 162 | EdgeIP string `json:"edgeIp"` 163 | EdgeLogsLink string `json:"edgeLogsLink"` 164 | ErrorCode string `json:"errorCode"` 165 | Hits int `json:"hits"` 166 | HTTPstatus int `json:"httpStatus"` 167 | ObjStatus string `json:"objStatus"` 168 | Region int `json:"region"` 169 | } 170 | 171 | type LogLines struct { 172 | Headers string `json:"headers"` 173 | Logs []string `json:"logs"` 174 | } 175 | 176 | type ResponseError struct { 177 | Type string `json:"type"` 178 | Title string `json:"title"` 179 | Status int `json:"status"` 180 | Detail string `json:"detail"` 181 | Errors []map[string]string `json:"errors"` 182 | } 183 | 184 | type IPinfoRecord struct { 185 | ID string `json:"id"` 186 | IP string `json:"ip"` 187 | IPtype string `json:"IPtype"` 188 | AssociatedDnsIp string `json:"associatedDnsIp"` 189 | Ecs string `json:"ecs"` 190 | Location map[string]string `json:"location"` 191 | } 192 | 193 | type DiagnosticRecord struct { 194 | EndUserDataId string `json:"endUserDataId"` 195 | Cipher string `json:"cipher"` 196 | Cookie bool `json:"cookie"` 197 | Protocol string `json:"protocol"` 198 | UserAgent string `json:"userAgent"` 199 | CreatedDate string `json:"createdDate"` 200 | UniqueId int `json:"uniqueId"` 201 | UserKey string `json:"userKey"` 202 | ClientDnsIpv6 *IPinfoRecord `json:"clientDnsIpv6"` 203 | EdgeIPs []IPinfoRecord `json:"edgeIps"` 204 | ClientIPv4 *IPinfoRecord `json:"clientIpv4"` 205 | ClientIPv6 *IPinfoRecord `json:"clientIpv6"` 206 | ClientDnsIpv4 *IPinfoRecord `json:"clientDnsIpv4"` 207 | PreferredClientIP *IPinfoRecord `json:"preferredClientIp"` 208 | } 209 | 210 | type UserDiagnosticData struct { 211 | GroupName string `json:"groupName"` 212 | CreatedDate string `json:"createdDate"` 213 | URL string `json:"url"` 214 | DiagnosticLink string `json:"diagnosticLink"` 215 | CaseIds []string `json:"caseIds"` 216 | Status string `json:"status"` 217 | DiagnosticRecords []DiagnosticRecord `json:"diagnosticRecords"` 218 | } 219 | 220 | type EndUserDiagnosticLink struct { 221 | DiagnosticLinkID string `json:"diagnosticLinkId"` 222 | GroupName string `json:"groupName"` 223 | CaseIds []string `json:"caseIds"` 224 | URL string `json:"url"` 225 | CreatedDate string `json:"createdDate"` 226 | DiagnosticLink string `json:"diagnosticLink"` 227 | Status string `json:"status"` 228 | RecordCount int `json:"recordCount"` 229 | DiagLinkCode string `json:"diagLinkCode"` 230 | } 231 | 232 | type CreateGroup struct { 233 | URL string `json:"url"` 234 | GroupName string `json:"groupName"` 235 | } 236 | 237 | type DnsRecord struct { 238 | Domain string `json:"domain"` 239 | Ttl int `json:"ttl"` 240 | RecordClass string `json:"recordClass"` 241 | RecordType string `json:"recordType"` 242 | PreferenceValue string `json:"preferenceValue"` 243 | Value string `json:"value"` 244 | } 245 | 246 | type ConnectivityProblemsRequest struct { 247 | Url string `json:"url"` 248 | EdgeLocationId string `json:"edgeLocationId"` 249 | SpoofEdgeIp string `json:"spoofEdgeIp,omitempty"` 250 | ClientIp string `json:"clientIp,omitempty"` 251 | RequestHeaders []string `json:"requestHeaders,omitempty"` 252 | IpVersion string `json:"ipVersion,omitempty"` 253 | PacketType string `json:"packetType,omitempty"` 254 | Port int `json:"port,omitempty"` 255 | } 256 | 257 | type ConnectivityProblemsResponse struct { 258 | ExecutionStatus string `json:"executionStatus"` 259 | RetryAfter int `json:"retryAfter"` 260 | Link string `json:"link"` 261 | } 262 | -------------------------------------------------------------------------------- /cmd/types_json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020. Akamai Technologies, Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | type GeoLocationJson struct { 18 | IpAddress string `json:"ipAddress"` 19 | ReportedTime string `json:"reportedTime"` 20 | GeoLocation *GeoLocation `json:"geoLocation"` 21 | } 22 | 23 | type VerifyIpJson struct { 24 | IpAddress string `json:"ipAddress"` 25 | ReportedTime string `json:"reportedTime"` 26 | IsCdnIp bool `json:"isCdnIp"` 27 | } 28 | 29 | type TranslateURLJson struct { 30 | Url string `json:"url"` 31 | ReportedTime string `json:"reportedTime"` 32 | TranlatedURL *TranslatedURL `json:"translatedUrl"` 33 | } 34 | 35 | type LogLinesJson struct { 36 | EdgeServerIp string `json:"edgeServerIp"` 37 | EndDate string `json:"endDate"` 38 | EndTime string `json:"endTime"` 39 | findIn []string `json:"findIn"` 40 | Duration int `json:"duration"` 41 | MaxLines int `json:"maxLines"` 42 | ClientRequest bool `json:"clientRequest"` 43 | ForwardRequest bool `json:"forwardRequest"` 44 | ReportedTime string `json:"reportedTime"` 45 | LogLines *LogLines `json:"logLines"` 46 | } 47 | 48 | type EstatsJson struct { 49 | UrlorCpCode string `json:"urlOrCpCode"` 50 | ReportedTime string `json:"reportedTime"` 51 | Estats *Estats `json:"eStats"` 52 | } 53 | 54 | type DebugUrlJson struct { 55 | Url string `json:"url"` 56 | EdgeIP string `json:"edgeIP"` 57 | Headers []string `json:"headers"` 58 | ReportedTime string `json:"reportedTime"` 59 | DebugUrl *DebugUrl `json:"urlDebug"` 60 | } 61 | 62 | type TranslatedErrorJosn struct { 63 | ErrorCode string `json:"errorCode"` 64 | ReportedTime string `json:"reportedTime"` 65 | TranslatedError *TranslatedError `json:"translatedError"` 66 | } 67 | 68 | type MtrDataJson struct { 69 | DestinationDomain string `json:"destinationDomain"` 70 | IpAddressOrLocationId string `json:"isAddressOrLocationId"` 71 | ResolveDns bool `json:"resolveDns"` 72 | ReportedTime string `json:"reportedTime"` 73 | Mtr *MtrData `json:"mtr"` 74 | } 75 | 76 | type DigInfoJson struct { 77 | HostName string `json:"hostName"` 78 | IpAddressOrLocationId string `json:"ipAdderssOrLocationId"` 79 | QueryType string `json:"queryType"` 80 | ReportedTime string `json:"reportedTime"` 81 | DigInfo *DigInfo `json:"digInfo"` 82 | } 83 | 84 | type EndUserDiagnosticLinkJson struct { 85 | ReportedTime string `json:"reportedTime"` 86 | EndUserDiagnosticLinks []EndUserDiagnosticLink `json:"endUserDiagnosticLinks"` 87 | } 88 | 89 | type DiagnosticLinkResponse struct { 90 | GroupName string `json:"groupName"` 91 | Url string `json:"url"` 92 | ReportedTime string `json:"reportedTime"` 93 | DiagnosticLink string `json:"diagnosticLink"` 94 | } 95 | 96 | type UserDiagnosticDataJson struct { 97 | LinkId string `json:"linkId"` 98 | ReportedTime string `json:"reportedTime"` 99 | UserDiagnosticData UserDiagnosticData `json:"endUserDiagnosticData"` 100 | } 101 | -------------------------------------------------------------------------------- /cmd/url_health_check.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var ( 9 | logs bool 10 | networkConnectivity bool 11 | urlHealthCheckRequest internal.UrlHealthCheckRequest 12 | ) 13 | 14 | var urlHealthCheckCmd = &cobra.Command{ 15 | Use: urlHealthCheckUse, 16 | Example: urlHealthCheckExample, 17 | Aliases: []string{"uhc", "debug-url"}, 18 | Args: cobra.MaximumNArgs(1), 19 | Run: func(cmd *cobra.Command, args []string) { 20 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 21 | api := internal.NewApiClient(*eghc) 22 | svc := internal.NewService(*api, cmd, globalFlags.json) 23 | validator := internal.NewValidator(cmd, jsonData) 24 | 25 | validator.ValidateUrlHealthCheckFields(args, portStr, &urlHealthCheckRequest, logs, networkConnectivity) 26 | 27 | svc.UrlHealthCheck(urlHealthCheckRequest) 28 | }, 29 | } 30 | 31 | func init() { 32 | 33 | rootCmd.AddCommand(urlHealthCheckCmd) 34 | urlHealthCheckCmd.Flags().SortFlags = false 35 | 36 | urlHealthCheckCmd.Short = internal.GetMessageForKey(urlHealthCheckCmd, internal.Short) 37 | urlHealthCheckCmd.Long = internal.GetMessageForKey(urlHealthCheckCmd, internal.Long) 38 | 39 | urlHealthCheckCmd.Flags().StringVarP(&urlHealthCheckRequest.EdgeLocationId, "client-location", "l", "", internal.GetMessageForKey(urlHealthCheckCmd, "client-location")) 40 | urlHealthCheckCmd.Flags().StringVarP(&urlHealthCheckRequest.EdgeIp, "edge-server-ip", "e", "", internal.GetMessageForKey(urlHealthCheckCmd, "edge-server-ip")) 41 | urlHealthCheckCmd.Flags().StringVarP(&portStr, "port", "p", "", internal.GetMessageForKey(urlHealthCheckCmd, "port")) 42 | urlHealthCheckCmd.Flags().StringVar(&urlHealthCheckRequest.PacketType, "packet-type", "", internal.GetMessageForKey(urlHealthCheckCmd, "packet-type")) 43 | urlHealthCheckCmd.Flags().StringVarP(&urlHealthCheckRequest.IpVersion, "ip-version", "i", "", internal.GetMessageForKey(urlHealthCheckCmd, "ip-version")) 44 | urlHealthCheckCmd.Flags().StringVarP(&urlHealthCheckRequest.QueryType, "query-type", "q", "", internal.GetMessageForKey(urlHealthCheckCmd, "query-type")) 45 | urlHealthCheckCmd.Flags().StringArrayVarP(&urlHealthCheckRequest.RequestHeaders, "request-header", "H", []string{}, internal.GetMessageForKey(urlHealthCheckCmd, "requestHeader")) 46 | urlHealthCheckCmd.Flags().BoolVar(&logs, "logs", false, internal.GetMessageForKey(urlHealthCheckCmd, "logs")) 47 | urlHealthCheckCmd.Flags().BoolVar(&networkConnectivity, "network-connectivity", false, internal.GetMessageForKey(urlHealthCheckCmd, "network-connectivity")) 48 | urlHealthCheckCmd.Flags().BoolVarP(&urlHealthCheckRequest.RunFromSiteShield, "run-from-site-shield-map", "r", false, internal.GetMessageForKey(urlHealthCheckCmd, "run-from-site-shield-map")) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /cmd/user_diagnostics.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var ( 9 | urlFlag string 10 | user string 11 | active bool 12 | linkId string 13 | mtr bool 14 | dig bool 15 | curl bool 16 | ) 17 | 18 | var userDiagnosticsCmd = &cobra.Command{ 19 | Use: userDiagnosticsUse, 20 | Aliases: []string{"ud"}, 21 | } 22 | 23 | func init() { 24 | rootCmd.AddCommand(userDiagnosticsCmd) 25 | 26 | userDiagnosticsCmd.Short = internal.GetMessageForKey(userDiagnosticsCmd, internal.Short) 27 | userDiagnosticsCmd.Long = internal.GetMessageForKey(userDiagnosticsCmd, internal.Long) 28 | 29 | } 30 | -------------------------------------------------------------------------------- /cmd/user_diagnostics_create.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var userDiagnosticsDataRequest internal.UserDiagnosticsDataRequest 9 | 10 | var userDiagnosticsCreateCmd = &cobra.Command{ 11 | Use: userDiagnosticsCreateUse, 12 | Example: userDiagnosticsCreateExample, 13 | Args: cobra.ExactArgs(0), 14 | Run: func(cmd *cobra.Command, args []string) { 15 | 16 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 17 | api := internal.NewApiClient(*eghc) 18 | svc := internal.NewService(*api, cmd, globalFlags.json) 19 | validator := internal.NewValidator(cmd, jsonData) 20 | 21 | validator.ValidateUserDiagnosticsCreateFields(args, &userDiagnosticsDataRequest) 22 | svc.UserDiagnosticsCreate(userDiagnosticsDataRequest) 23 | 24 | }, 25 | } 26 | 27 | func init() { 28 | 29 | userDiagnosticsCmd.AddCommand(userDiagnosticsCreateCmd) 30 | userDiagnosticsCreateCmd.Flags().SortFlags = false 31 | 32 | userDiagnosticsCreateCmd.Short = internal.GetMessageForKey(userDiagnosticsCreateCmd, internal.Short) 33 | userDiagnosticsCreateCmd.Long = internal.GetMessageForKey(userDiagnosticsCreateCmd, internal.Long) 34 | 35 | userDiagnosticsCreateCmd.Flags().StringVarP(&userDiagnosticsDataRequest.Url, "url", "u", "", internal.GetMessageForKey(userDiagnosticsCreateCmd, "url")) 36 | userDiagnosticsCreateCmd.Flags().StringVarP(&userDiagnosticsDataRequest.IpaHostname, "ipa-hostname", "", "", internal.GetMessageForKey(userDiagnosticsCreateCmd, "ipa-hostname")) 37 | userDiagnosticsCreateCmd.Flags().StringVarP(&userDiagnosticsDataRequest.Note, "notes", "n", "", internal.GetMessageForKey(userDiagnosticsCreateCmd, "notes")) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /cmd/user_diagnostics_get.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var userDiagnosticsGetCmd = &cobra.Command{ 9 | Use: userDiagnosticsGetUse, 10 | Example: userDiagnosticsGetExample, 11 | Args: cobra.ExactArgs(1), 12 | Run: func(cmd *cobra.Command, args []string) { 13 | 14 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 15 | api := internal.NewApiClient(*eghc) 16 | svc := internal.NewService(*api, cmd, globalFlags.json) 17 | 18 | linkId = args[0] 19 | svc.UserDiagnosticsGet(linkId, mtr, dig, curl) 20 | 21 | }, 22 | } 23 | 24 | func init() { 25 | 26 | userDiagnosticsCmd.AddCommand(userDiagnosticsGetCmd) 27 | userDiagnosticsGetCmd.Flags().SortFlags = false 28 | 29 | userDiagnosticsGetCmd.Short = internal.GetMessageForKey(userDiagnosticsGetCmd, internal.Short) 30 | userDiagnosticsGetCmd.Long = internal.GetMessageForKey(userDiagnosticsGetCmd, internal.Long) 31 | 32 | userDiagnosticsGetCmd.Flags().BoolVar(&mtr, "mtr", false, internal.GetMessageForKey(userDiagnosticsGetCmd, "mtr")) 33 | userDiagnosticsGetCmd.Flags().BoolVar(&curl, "curl", false, internal.GetMessageForKey(userDiagnosticsGetCmd, "curl")) 34 | userDiagnosticsGetCmd.Flags().BoolVar(&dig, "dig", false, internal.GetMessageForKey(userDiagnosticsGetCmd, "dig")) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /cmd/user_diagnostics_list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var userDiagnosticsListCmd = &cobra.Command{ 9 | Use: userDiagnosticsListUse, 10 | Example: userDiagnosticsListExample, 11 | Args: cobra.ExactArgs(0), 12 | Run: func(cmd *cobra.Command, args []string) { 13 | 14 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 15 | api := internal.NewApiClient(*eghc) 16 | svc := internal.NewService(*api, cmd, globalFlags.json) 17 | 18 | svc.UserDiagnosticsList(urlFlag, user, active) 19 | 20 | }, 21 | } 22 | 23 | func init() { 24 | 25 | userDiagnosticsCmd.AddCommand(userDiagnosticsListCmd) 26 | userDiagnosticsListCmd.Flags().SortFlags = false 27 | 28 | userDiagnosticsListCmd.Short = internal.GetMessageForKey(userDiagnosticsListCmd, internal.Short) 29 | userDiagnosticsListCmd.Long = internal.GetMessageForKey(userDiagnosticsListCmd, internal.Long) 30 | 31 | userDiagnosticsListCmd.Flags().StringVarP(&urlFlag, "url", "u", "", internal.GetMessageForKey(userDiagnosticsListCmd, "url")) 32 | userDiagnosticsListCmd.Flags().StringVar(&user, "user", "", internal.GetMessageForKey(userDiagnosticsListCmd, "user")) 33 | userDiagnosticsListCmd.Flags().BoolVarP(&active, "active", "a", false, internal.GetMessageForKey(userDiagnosticsListCmd, "active")) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /cmd/verify_ip.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var verifyIpsRequest internal.VerifyLocateIpsRequest 9 | 10 | var verifyIpCmd = &cobra.Command{ 11 | Use: verifyIpUse, 12 | Example: verifyIpExample, 13 | Aliases: []string{"vi"}, 14 | Run: func(cmd *cobra.Command, args []string) { 15 | 16 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 17 | api := internal.NewApiClient(*eghc) 18 | svc := internal.NewService(*api, cmd, globalFlags.json) 19 | validator := internal.NewValidator(cmd, jsonData) 20 | 21 | validator.ValidateVerifyIpOrLocateIpFields(args, &verifyIpsRequest) 22 | svc.VerifyIp(verifyIpsRequest) 23 | 24 | }, 25 | } 26 | 27 | func init() { 28 | 29 | rootCmd.AddCommand(verifyIpCmd) 30 | verifyIpCmd.Flags().SortFlags = false 31 | 32 | verifyIpCmd.Short = internal.GetMessageForKey(verifyIpCmd, internal.Short) 33 | verifyIpCmd.Long = internal.GetMessageForKey(verifyIpCmd, internal.Long) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /cmd/verify_locate_ip.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/internal" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var verifyLocateIpRequest internal.VerifyLocateIpRequest 9 | 10 | var verifyLocateIpCmd = &cobra.Command{ 11 | Use: verifyLocateIpUse, 12 | Example: verifyLocateIpExample, 13 | Aliases: []string{"vli"}, 14 | Args: cobra.MaximumNArgs(1), 15 | Run: func(cmd *cobra.Command, args []string) { 16 | 17 | eghc := internal.NewEdgeGridHttpClient(globalFlags.edgeRcPath, globalFlags.edgeRcSection, globalFlags.accountSwitchKey) 18 | api := internal.NewApiClient(*eghc) 19 | svc := internal.NewService(*api, cmd, globalFlags.json) 20 | validator := internal.NewValidator(cmd, jsonData) 21 | 22 | validator.ValidateVerifyLocateIpFields(args, &verifyLocateIpRequest) 23 | svc.VerifyLocateIp(verifyLocateIpRequest) 24 | 25 | }, 26 | } 27 | 28 | func init() { 29 | 30 | rootCmd.AddCommand(verifyLocateIpCmd) 31 | verifyLocateIpCmd.Flags().SortFlags = false 32 | 33 | verifyLocateIpCmd.Short = internal.GetMessageForKey(verifyLocateIpCmd, internal.Short) 34 | verifyLocateIpCmd.Long = internal.GetMessageForKey(verifyLocateIpCmd, internal.Long) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/akamai/cli-diagnostics 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 7 | github.com/fatih/color v1.13.0 8 | github.com/mitchellh/go-homedir v1.1.0 9 | github.com/spf13/cobra v1.2.1 10 | ) 11 | 12 | require ( 13 | github.com/google/uuid v1.1.2 // indirect 14 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 15 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect 16 | github.com/mattn/go-colorable v0.1.9 // indirect 17 | github.com/mattn/go-isatty v0.0.14 // indirect 18 | github.com/mattn/go-runewidth v0.0.9 // indirect 19 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 20 | github.com/spf13/pflag v1.0.5 // indirect 21 | github.com/tidwall/gjson v1.9.3 22 | golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect 23 | gopkg.in/ini.v1 v1.63.2 // indirect 24 | ) 25 | 26 | require ( 27 | github.com/briandowns/spinner v1.15.0 28 | github.com/fatih/camelcase v1.0.0 29 | github.com/oleiade/reflections v1.0.1 30 | github.com/olekukonko/tablewriter v0.0.5 31 | github.com/sirupsen/logrus v1.4.2 32 | github.com/tidwall/match v1.1.1 // indirect 33 | github.com/tidwall/pretty v1.2.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /internal/api_client.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "strconv" 9 | 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type ApiClient struct { 14 | client EdgeGridHttpClient 15 | } 16 | 17 | func NewApiClient(client EdgeGridHttpClient) *ApiClient { 18 | return &ApiClient{client} 19 | } 20 | 21 | // Adds query parameters to url. 22 | // QueryMap entries with empty values are ignored 23 | func addQueryParams(url *url.URL, queryMap map[string]string) { 24 | queryParams := url.Query() 25 | 26 | log.Debug("Adding query parameters to url %s", url) 27 | for k, v := range queryMap { 28 | log.Tracef("Processing query parameter - [%s]:[%s]", k, v) 29 | 30 | if v != "" { 31 | queryParams.Set(k, v) 32 | } 33 | } 34 | 35 | url.RawQuery = queryParams.Encode() 36 | log.Tracef("Url with query parameters: %s", url.String()) 37 | } 38 | 39 | func (api ApiClient) LocateIp(locateIpsRequest VerifyLocateIpsRequest) (*[]byte, *CliError) { 40 | 41 | locateIpsRequestBytes, err := json.Marshal(locateIpsRequest) 42 | if err != nil { 43 | log.Error(err) 44 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 45 | } 46 | 47 | path := "/edge-diagnostics/v1/locate-ip" 48 | var requestHeaders = make(http.Header) 49 | requestHeaders.Add(ContentType, ApplicationJson) 50 | 51 | resp, byt := api.client.request(Post, path, &locateIpsRequestBytes, requestHeaders) 52 | if resp.StatusCode == http.StatusOK { 53 | return byt, nil 54 | } 55 | log.Debug("api error response", string(*byt)) 56 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting geolocation details.") 57 | } 58 | 59 | func (api ApiClient) VerifyIp(verifyIpsRequest VerifyLocateIpsRequest) (*[]byte, *CliError) { 60 | 61 | verifyIpsRequestBytes, err := json.Marshal(verifyIpsRequest) 62 | if err != nil { 63 | log.Error(err) 64 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 65 | } 66 | 67 | path := "/edge-diagnostics/v1/verify-edge-ip" 68 | var requestHeaders = make(http.Header) 69 | requestHeaders.Add(ContentType, ApplicationJson) 70 | 71 | resp, byt := api.client.request(Post, path, &verifyIpsRequestBytes, requestHeaders) 72 | if resp.StatusCode == http.StatusOK { 73 | return byt, nil 74 | } 75 | log.Debug("api error response", string(*byt)) 76 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in verifying IP address.") 77 | } 78 | 79 | // Uncomment when verify-locate-ip is OPEN 80 | 81 | func (api ApiClient) VerifyLocateIp(verifyLocateIpRequest VerifyLocateIpRequest) (*[]byte, *CliError) { 82 | 83 | locateIpRequestBytes, err := json.Marshal(verifyLocateIpRequest) 84 | if err != nil { 85 | log.Error(err) 86 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 87 | } 88 | path := "/edge-diagnostics/v1/verify-locate-ip" 89 | var requestHeaders = make(http.Header) 90 | requestHeaders.Add(ContentType, ApplicationJson) 91 | 92 | resp, byt := api.client.request(Post, path, &locateIpRequestBytes, requestHeaders) 93 | if resp.StatusCode == http.StatusOK { 94 | return byt, nil 95 | } 96 | log.Debug("api error response", string(*byt)) 97 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in verifying and getting the geolocation details.") 98 | } 99 | 100 | func (api ApiClient) Dig(digRequest DigRequest) (*[]byte, *CliError) { 101 | 102 | digRequestBytes, err := json.Marshal(digRequest) 103 | if err != nil { 104 | log.Error(err) 105 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 106 | } 107 | path := "/edge-diagnostics/v1/dig" 108 | var requestHeaders = make(http.Header) 109 | requestHeaders.Add(ContentType, ApplicationJson) 110 | 111 | resp, byt := api.client.request(Post, path, &digRequestBytes, requestHeaders) 112 | if resp.StatusCode == http.StatusOK { 113 | return byt, nil 114 | } 115 | log.Debug("api error response\n", string(*byt)) 116 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error getting dig response.") 117 | } 118 | 119 | func (api ApiClient) UserDiagnosticsCreate(userDiagnosticsDataRequest UserDiagnosticsDataRequest) (*[]byte, *CliError) { 120 | userDiagnosticsDataRequestBytes, err := json.Marshal(userDiagnosticsDataRequest) 121 | if err != nil { 122 | log.Error(err) 123 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 124 | } 125 | 126 | path := "/edge-diagnostics/v1/user-diagnostic-data/groups" 127 | var requestHeaders = make(http.Header) 128 | requestHeaders.Add(ContentType, ApplicationJson) 129 | 130 | resp, byt := api.client.request(Post, path, &userDiagnosticsDataRequestBytes, requestHeaders) 131 | if resp.StatusCode == http.StatusCreated { 132 | return byt, nil 133 | } 134 | log.Debug("api error response", string(*byt)) 135 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in generating diagnostic link") 136 | 137 | } 138 | 139 | func (api ApiClient) Curl(curlRequest CurlRequest) (*[]byte, *CliError) { 140 | 141 | curlRequestBytes, err := json.Marshal(curlRequest) 142 | if err != nil { 143 | log.Error(err) 144 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 145 | } 146 | 147 | // get response 148 | path := "/edge-diagnostics/v1/curl" 149 | var requestHeaders = make(http.Header) 150 | requestHeaders.Add(ContentType, ApplicationJson) 151 | 152 | resp, byt := api.client.request(Post, path, &curlRequestBytes, requestHeaders) 153 | if resp.StatusCode == http.StatusOK { 154 | return byt, nil 155 | } 156 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting curl results.") 157 | 158 | } 159 | 160 | func (api ApiClient) Estats(estatsRequest EstatsRequest) (*[]byte, *CliError) { 161 | 162 | estatsRequestBytes, err := json.Marshal(estatsRequest) 163 | if err != nil { 164 | log.Error(err) 165 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 166 | } 167 | 168 | // get response 169 | path := "/edge-diagnostics/v1/estats" 170 | var requestHeaders = make(http.Header) 171 | requestHeaders.Add(ContentType, ApplicationJson) 172 | 173 | resp, byt := api.client.request(Post, path, &estatsRequestBytes, requestHeaders) 174 | if resp.StatusCode == http.StatusOK { 175 | return byt, nil 176 | } 177 | log.Debug("api error response", string(*byt)) 178 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting estats results.") 179 | 180 | } 181 | 182 | func (api ApiClient) UserDiagnosticsList(urlFilter, user string, active bool) (*[]byte, *CliError) { 183 | 184 | path := "/edge-diagnostics/v1/user-diagnostic-data/groups" 185 | parsedUrl, _ := url.Parse(path) 186 | 187 | // add optional query parameters 188 | queryMap := map[string]string{ 189 | "url": urlFilter, 190 | "user": user, 191 | "activeOnly": strconv.FormatBool(active), 192 | } 193 | addQueryParams(parsedUrl, queryMap) 194 | 195 | // get response 196 | resp, byt := api.client.request(Get, parsedUrl.String(), nil, nil) 197 | if resp.StatusCode == http.StatusOK { 198 | return byt, nil 199 | } 200 | log.Debug("api error response", string(*byt)) 201 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting list of diagnostic links.") 202 | } 203 | 204 | func (api ApiClient) UserDiagnosticsGet(linkId string, mtr, dig, curl bool) (*[]byte, *CliError) { 205 | 206 | path := fmt.Sprintf("/edge-diagnostics/v1/user-diagnostic-data/groups/%s/records", linkId) 207 | parsedUrl, _ := url.Parse(path) 208 | 209 | // add optional query parameters 210 | queryMap := map[string]string{ 211 | "includeMtr": strconv.FormatBool(mtr), 212 | "includeDig": strconv.FormatBool(dig), 213 | "includeCurl": strconv.FormatBool(curl), 214 | } 215 | addQueryParams(parsedUrl, queryMap) 216 | 217 | // get response 218 | resp, byt := api.client.request(Get, parsedUrl.String(), nil, nil) 219 | if resp.StatusCode == http.StatusOK { 220 | return byt, nil 221 | } 222 | log.Debug("api error response", string(*byt)) 223 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting diagnostic link") 224 | } 225 | 226 | func (api ApiClient) TranslateUrl(arlRequest ArlRequest) (*[]byte, *CliError) { 227 | 228 | arlRequestBytes, err := json.Marshal(arlRequest) 229 | if err != nil { 230 | log.Error(err) 231 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 232 | } 233 | 234 | path := "/edge-diagnostics/v1/translated-url" 235 | var requestHeaders = make(http.Header) 236 | requestHeaders.Add(ContentType, ApplicationJson) 237 | 238 | resp, byt := api.client.request(Post, path, &arlRequestBytes, requestHeaders) 239 | if resp.StatusCode == http.StatusOK { 240 | return byt, nil 241 | } 242 | log.Debug("api error response", string(*byt)) 243 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in translating url") 244 | } 245 | 246 | func (api ApiClient) TranslateErrorPost(errorTranslatorRequest ErrorTranslatorRequest) (*[]byte, *CliError) { 247 | 248 | errorTranslatorRequestBytes, err := json.Marshal(errorTranslatorRequest) 249 | if err != nil { 250 | log.Error(err) 251 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 252 | } 253 | 254 | path := "/edge-diagnostics/v1/error-translator" 255 | var requestHeaders = make(http.Header) 256 | requestHeaders.Add(ContentType, ApplicationJson) 257 | 258 | resp, byt := api.client.request(Post, path, &errorTranslatorRequestBytes, requestHeaders) 259 | if resp.StatusCode == http.StatusAccepted { 260 | return byt, nil 261 | } 262 | log.Debug("api error response", string(*byt)) 263 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting logs for error string.") 264 | } 265 | 266 | func (api ApiClient) TranslateErrorGet(link string) (*[]byte, *CliError) { 267 | 268 | var requestHeaders = make(http.Header) 269 | requestHeaders.Add(ContentType, ApplicationJson) 270 | 271 | resp, byt := api.client.request(Get, link, nil, requestHeaders) 272 | if resp.StatusCode == http.StatusOK { 273 | return byt, nil 274 | } 275 | log.Debug("api error response", string(*byt)) 276 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting logs for error string.") 277 | } 278 | 279 | func (api ApiClient) UrlHealthCheckPost(urlHealthCheckRequest UrlHealthCheckRequest) (*[]byte, *CliError) { 280 | 281 | urlHealthCheckRequestBytes, err := json.Marshal(urlHealthCheckRequest) 282 | if err != nil { 283 | log.Error(err) 284 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 285 | } 286 | 287 | path := "/edge-diagnostics/v1/url-health-check" 288 | var requestHeaders = make(http.Header) 289 | requestHeaders.Add(ContentType, ApplicationJson) 290 | 291 | resp, byt := api.client.request(Post, path, &urlHealthCheckRequestBytes, requestHeaders) 292 | if resp.StatusCode == http.StatusAccepted { 293 | return byt, nil 294 | } 295 | log.Debug("api error response", string(*byt)) 296 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting url health check.") 297 | } 298 | 299 | func (api ApiClient) UrlHealthCheckGet(link string) (*[]byte, *CliError) { 300 | 301 | var requestHeaders = make(http.Header) 302 | requestHeaders.Add(ContentType, ApplicationJson) 303 | 304 | resp, byt := api.client.request(Get, link+"?includeContentResponseBody=true", nil, requestHeaders) 305 | if resp.StatusCode == http.StatusOK { 306 | return byt, nil 307 | } 308 | log.Debug("api error response", string(*byt)) 309 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting url health check.") 310 | } 311 | 312 | func (api ApiClient) EdgeLocations() (*[]byte, *CliError) { 313 | path := "/edge-diagnostics/v1/edge-locations" 314 | var requestHeaders = make(http.Header) 315 | requestHeaders.Add(ContentType, ApplicationJson) 316 | 317 | resp, byt := api.client.request(Get, path, nil, requestHeaders) 318 | 319 | if resp.StatusCode == http.StatusOK { 320 | return byt, nil 321 | } 322 | log.Debug("api error response", string(*byt)) 323 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting edge locations") 324 | } 325 | 326 | func (api ApiClient) IpaHostnames() (*[]byte, *CliError) { 327 | path := "/edge-diagnostics/v1/ipa/hostnames" 328 | var requestHeaders = make(http.Header) 329 | requestHeaders.Add(ContentType, ApplicationJson) 330 | 331 | resp, byt := api.client.request(Get, path, nil, requestHeaders) 332 | 333 | if resp.StatusCode == http.StatusOK { 334 | return byt, nil 335 | } 336 | log.Debug("api error response", string(*byt)) 337 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting ipa hostnames") 338 | } 339 | 340 | func (api ApiClient) GtmHostnames() (*[]byte, *CliError) { 341 | path := "/edge-diagnostics/v1/gtm/gtm-properties" 342 | var requestHeaders = make(http.Header) 343 | requestHeaders.Add(ContentType, ApplicationJson) 344 | 345 | resp, byt := api.client.request(Get, path, nil, requestHeaders) 346 | 347 | if resp.StatusCode == http.StatusOK { 348 | return byt, nil 349 | } 350 | log.Debug("api error response", string(*byt)) 351 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting gtm hostnames") 352 | } 353 | 354 | func (api ApiClient) GtmTestTargetIp(property string, domain string) (*[]byte, *CliError) { 355 | path := "/edge-diagnostics/v1/gtm/" + property + "/" + domain + "/gtm-property-ips" 356 | var requestHeaders = make(http.Header) 357 | requestHeaders.Add(ContentType, ApplicationJson) 358 | 359 | resp, byt := api.client.request(Get, path, nil, requestHeaders) 360 | 361 | if resp.StatusCode == http.StatusOK { 362 | return byt, nil 363 | } 364 | log.Debug("api error response", string(*byt)) 365 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting gtm test target ip") 366 | } 367 | 368 | func (api ApiClient) Mtr(mtrRequest MtrRequest) (*[]byte, *CliError) { 369 | 370 | mtrRequestBytes, err := json.Marshal(mtrRequest) 371 | if err != nil { 372 | log.Error(err) 373 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 374 | } 375 | 376 | path := "/edge-diagnostics/v1/mtr" 377 | var requestHeaders = make(http.Header) 378 | requestHeaders.Add(ContentType, ApplicationJson) 379 | 380 | resp, byt := api.client.request(Post, path, &mtrRequestBytes, requestHeaders) 381 | if resp.StatusCode == http.StatusOK { 382 | return byt, nil 383 | } 384 | log.Debug("api error response", string(*byt)) 385 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting mtr results.") 386 | } 387 | 388 | func (api ApiClient) GrepPost(grepRequest GrepRequest) (*[]byte, *CliError) { 389 | 390 | grepRequestBytes, err := json.Marshal(grepRequest) 391 | if err != nil { 392 | log.Error(err) 393 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 394 | } 395 | 396 | path := "/edge-diagnostics/v1/grep" 397 | var requestHeaders = make(http.Header) 398 | requestHeaders.Add(ContentType, ApplicationJson) 399 | 400 | resp, byt := api.client.request(Post, path, &grepRequestBytes, requestHeaders) 401 | if resp.StatusCode == http.StatusAccepted { 402 | return byt, nil 403 | } 404 | log.Debug("api error response", string(*byt)) 405 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting grep logs.") 406 | } 407 | 408 | func (api ApiClient) GrepGet(link string) (*[]byte, *CliError) { 409 | 410 | var requestHeaders = make(http.Header) 411 | requestHeaders.Add(ContentType, ApplicationJson) 412 | 413 | resp, byt := api.client.request(Get, link, nil, requestHeaders) 414 | if resp.StatusCode == http.StatusOK { 415 | return byt, nil 416 | } 417 | log.Debug("api error response", string(*byt)) 418 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in getting grep logs.") 419 | } 420 | 421 | func (api ApiClient) ConnectivityProblemsPost(connectivityProblemsRequest ConnectivityProblemsRequest) (*[]byte, *CliError) { 422 | 423 | connectivityProblemsRequestBytes, err := json.Marshal(connectivityProblemsRequest) 424 | if err != nil { 425 | log.Error(err) 426 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 427 | } 428 | 429 | path := "/edge-diagnostics/v1/connectivity-problems" 430 | var requestHeaders = make(http.Header) 431 | requestHeaders.Add(ContentType, ApplicationJson) 432 | 433 | resp, byt := api.client.request(Post, path, &connectivityProblemsRequestBytes, requestHeaders) 434 | if resp.StatusCode == http.StatusAccepted { 435 | return byt, nil 436 | } 437 | log.Debug("api error response", string(*byt)) 438 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in running connectivity problems.") 439 | } 440 | 441 | func (api ApiClient) ConnectivityProblemsGet(link string) (*[]byte, *CliError) { 442 | 443 | var requestHeaders = make(http.Header) 444 | requestHeaders.Add(ContentType, ApplicationJson) 445 | 446 | resp, byt := api.client.request(Get, link+"?includeContentResponseBody=true", nil, requestHeaders) 447 | if resp.StatusCode == http.StatusOK { 448 | return byt, nil 449 | } 450 | log.Debug("api error response", string(*byt)) 451 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in running connectivity problems.") 452 | } 453 | 454 | func (api ApiClient) ContentProblemsPost(contentProblemsRequest ContentProblemsRequest) (*[]byte, *CliError) { 455 | 456 | contentProblemsRequestBytes, err := json.Marshal(contentProblemsRequest) 457 | if err != nil { 458 | log.Error(err) 459 | Abort(GetGlobalErrorMessage(RequestParsingError), ParsingErrExitCode) 460 | } 461 | 462 | path := "/edge-diagnostics/v1/content-problems" 463 | var requestHeaders = make(http.Header) 464 | requestHeaders.Add(ContentType, ApplicationJson) 465 | 466 | resp, byt := api.client.request(Post, path, &contentProblemsRequestBytes, requestHeaders) 467 | if resp.StatusCode == http.StatusAccepted { 468 | return byt, nil 469 | } 470 | log.Debug("api error response", string(*byt)) 471 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in running content problems.") 472 | } 473 | 474 | func (api ApiClient) ContentProblemsGet(link string) (*[]byte, *CliError) { 475 | 476 | var requestHeaders = make(http.Header) 477 | requestHeaders.Add(ContentType, ApplicationJson) 478 | 479 | resp, byt := api.client.request(Get, link+"?includeContentResponseBody=true", nil, requestHeaders) 480 | if resp.StatusCode == http.StatusOK { 481 | return byt, nil 482 | } 483 | log.Debug("api error response", string(*byt)) 484 | return nil, CliErrorFromPulsarProblemObject(*byt, resp.StatusCode, "Error in running content problems.") 485 | } 486 | -------------------------------------------------------------------------------- /internal/cobra_customisation.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "text/template" 5 | ) 6 | 7 | var UsageFuncMap = template.FuncMap{ 8 | "bold": bold, 9 | "header": header, 10 | "italic": italic, 11 | "capsToTitle": CapsToTitle, 12 | "green": success, 13 | "blue": blue, 14 | } 15 | 16 | var CustomUsageTemplate = `{{header "Usage:"}}{{if .Runnable}} 17 | {{blue "akamai " .UseLine}}{{end}}{{if .HasAvailableSubCommands}} 18 | {{blue "akamai " .CommandPath " [command]"}}{{end}}{{if gt (len .Aliases) 0}} 19 | 20 | {{header "Aliases:"}} 21 | {{.NameAndAliases}}{{end}}{{if .HasExample}} 22 | 23 | {{header "Examples:"}} 24 | {{.Example}}{{end}}{{if .HasAvailableSubCommands}} 25 | 26 | {{header "Available Commands:"}}{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} 27 | {{rpad .Name .NamePadding | green}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} 28 | 29 | {{header "Flags:"}} 30 | {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} 31 | 32 | {{header "Global Flags:"}} 33 | {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} 34 | 35 | {{header "Additional help topics:"}}{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} 36 | {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} 37 | 38 | Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} 39 | ` 40 | -------------------------------------------------------------------------------- /internal/constants.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | // HTTP headers 4 | const ( 5 | ContentType = "Content-Type" 6 | ) 7 | 8 | // HTTP header values 9 | const ( 10 | ApplicationJson = "application/json" 11 | ) 12 | 13 | const ( 14 | X_ED_CLIENT_TYPE = "X-ED-CLIENT-TYPE" 15 | ) 16 | 17 | // constant message keys 18 | const ( 19 | Short = "short" 20 | Long = "long" 21 | Empty = "" 22 | Global = "global" 23 | Missing = "missing" 24 | Exclusive = "exclusive" 25 | Invalid = "invalid" 26 | Redundant = "redundant" 27 | RequestParsingError = "requestParsingError" 28 | ResponseParsingError = "responseParsingError" 29 | SpinnerMessage = "spinnerMessage" 30 | MissingArgs = "missingArgs" 31 | FieldsNotRequired = "fieldsNotRequired" 32 | DefaultPort = 80 33 | IPV4 = "IPV4" 34 | IPV6 = "IPV6" 35 | TCP = "TCP" 36 | ICMP = "ICMP" 37 | FailedIps = "failedIps" 38 | ) 39 | 40 | // HTTP request methods 41 | const ( 42 | Get = "GET" 43 | Post = "POST" 44 | ) 45 | 46 | // JSON output 47 | const ( 48 | indentString = "\t" 49 | ) 50 | 51 | // Geolocation fields 52 | const ( 53 | geographicLocation = "GEOGRAPHIC LOCATION" 54 | ipAddress = "IP address" 55 | countryCode = "Country code" 56 | regionCode = "Region code" 57 | city = "City" 58 | dma = "DMA" 59 | msa = "MSA" 60 | pmsa = "PMSA" 61 | areaCode = "Area code" 62 | latitude = "Latitude" 63 | longitude = "Longitude" 64 | county = "County" 65 | continent = "Continent" 66 | fisp = "FIPS" 67 | timeZone = "Time zone" 68 | zipCode = "Zip code" 69 | proxy = "Proxy" 70 | 71 | networkLocation = "NETWORK LOCATION" 72 | network = "Network" 73 | networkType = "Network type" 74 | asNum = "ASN" 75 | throughput = "Throughput" 76 | ) 77 | 78 | // User diagnostics data table 79 | const ( 80 | userDiagnosticsListNote = "NOTE: Each link is active for 7 days and has a limit of 50 submissions." 81 | linkId = "LINK ID" 82 | hostNameOrUrl = "URL" 83 | statusUserDiagnosticsLink = "LINK STATUS" 84 | diagnosticLink = "DIAGNOSTIC LINK" 85 | results = "RESULTS" 86 | user = "user" 87 | requestDate = "REQUEST DATE" 88 | ) 89 | 90 | // Edge Locations 91 | const ( 92 | Search = "search" 93 | edgeLocations = "EDGE SERVER LOCATIONS" 94 | ) 95 | 96 | // Ipa Hostnames 97 | const ( 98 | ipaHostnames = "IP ACCELERATION HOSTNAMES" 99 | ipaHostname = "IP ACCELERATION HOSTNAME" 100 | ) 101 | 102 | // Gtm Hostnames 103 | const ( 104 | TestTargetIp = "testTargetIp" 105 | gtmHostnames = "GTM HOSTNAMES" 106 | gtmHostname = "GTM HOSTNAME" 107 | testIp = "TEST IP" 108 | target = "TARGET" 109 | ) 110 | 111 | // Mtr constants 112 | const ( 113 | sourceTypeLocation = "LOCATION" 114 | sourceTypeEdgeIp = "EDGE_IP" 115 | destinationTypeHost = "HOST" 116 | destinationTypeIp = "IP" 117 | mtrTableCol = "Loss %, Sent, Last, Avg, Best, Worst, StDev, Location" 118 | ) 119 | 120 | const curlHeaderSeparator = ":" 121 | 122 | const ( 123 | CliErrExitCode int = 1 124 | CmdErrExitCode int = 2 125 | ParsingErrExitCode int = 3 126 | ) 127 | 128 | const HttpCodeExitCodeDiff = 300 129 | -------------------------------------------------------------------------------- /internal/edge_grid_http_client.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | "strings" 9 | 10 | "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type EdgeGridHttpClient struct { 15 | config edgegrid.Config 16 | accountSwitchKey string 17 | } 18 | 19 | func NewEdgeGridHttpClient(filepath, section, accountSwitchKey string) *EdgeGridHttpClient { 20 | //checks for ENV variables, if not present looks for .edgerc file 21 | config, err := edgegrid.Init(filepath, section) 22 | if err != nil { 23 | Abort(GetGlobalErrorMessage("initEdgeRc"), CliErrExitCode) 24 | } 25 | return &EdgeGridHttpClient{config, accountSwitchKey} 26 | } 27 | 28 | func (h EdgeGridHttpClient) request(method string, path string, payload *[]byte, headers http.Header) (*http.Response, *[]byte) { 29 | var ( 30 | err error 31 | req *http.Request 32 | client = http.Client{} 33 | ) 34 | 35 | var protocol = "https://" 36 | if strings.Contains(h.config.Host, "http") { 37 | protocol = "" // For mocking API calls locally 38 | } 39 | 40 | parsedPath, _ := url.Parse(path) 41 | if h.accountSwitchKey != "" { 42 | log.Debugf("Account switch key present :: %s. Adding to URL.", h.accountSwitchKey) 43 | query := parsedPath.Query() 44 | query.Set("accountSwitchKey", h.accountSwitchKey) 45 | parsedPath.RawQuery = query.Encode() 46 | } 47 | 48 | log.Debugf("Sending request:: %s %s, Headers: %v, Body: %s\n", method, parsedPath, headers, payload) 49 | 50 | if payload != nil { 51 | req, err = http.NewRequest(method, protocol+h.config.Host+parsedPath.String(), bytes.NewBuffer(*payload)) 52 | if err != nil { 53 | Abort(err.Error(), ParsingErrExitCode) 54 | } 55 | } else { 56 | req, err = http.NewRequest(method, protocol+h.config.Host+parsedPath.String(), nil) 57 | if err != nil { 58 | Abort(err.Error(), ParsingErrExitCode) 59 | } 60 | } 61 | 62 | if headers != nil { 63 | req.Header = headers 64 | } else { 65 | req.Header = make(http.Header) 66 | } 67 | req.Header.Add(X_ED_CLIENT_TYPE, "CLI") 68 | 69 | req = edgegrid.AddRequestHeader(h.config, req) 70 | 71 | resp, er := client.Do(req) 72 | if er != nil { 73 | Abort(err.Error(), CliErrExitCode) 74 | } 75 | defer resp.Body.Close() 76 | 77 | byt, err := ioutil.ReadAll(resp.Body) 78 | if err != nil { 79 | Abort(err.Error(), ParsingErrExitCode) 80 | } 81 | log.Debugf("Received response:: Status: %d\n", resp.StatusCode) 82 | log.Tracef("Response body: %s\n", byt) 83 | 84 | return resp, &byt 85 | } 86 | -------------------------------------------------------------------------------- /internal/errors.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | // ApiError object for API error payloads 11 | type ApiError struct { 12 | Type string `json:"type"` 13 | Title string `json:"title"` 14 | Status int `json:"status"` 15 | Instance string `json:"instance"` 16 | Detail string `json:"detail"` 17 | Errors []ApiSubError `json:"errors"` 18 | } 19 | 20 | // ApiSubError object represents sub-errors of an error payload or error response in 207 21 | type ApiSubError struct { 22 | Type string `json:"type"` 23 | Title string `json:"title"` 24 | RequestField string `json:"requestField"` 25 | RequestValues []interface{} `json:"requestValues"` 26 | Detail string `json:"detail"` 27 | } 28 | 29 | // CliError is used to transmit errors across the app 30 | type CliError struct { 31 | apiError *ApiError 32 | apiSubErrors []ApiSubError 33 | errorMessage string 34 | responseCode int 35 | } 36 | 37 | func CliErrorWithMessage(message string) *CliError { 38 | return &CliError{errorMessage: message} 39 | } 40 | 41 | func CliErrorFromPulsarProblemObject(apiErrorByte []byte, responseCode int, fallbackMessage string) *CliError { 42 | var cliError CliError 43 | cliError.responseCode = responseCode 44 | 45 | // set error message as fallback message by default 46 | cliError.errorMessage = fallbackMessage 47 | 48 | var apiError ApiError 49 | apiParsingError := json.Unmarshal(apiErrorByte, &apiError) 50 | if apiParsingError != nil { 51 | log.Debugf("Failed to parse api error response: [%s]", apiParsingError) 52 | if GetGlobalErrorMessage(fmt.Sprint(responseCode)) != GetGlobalFallBackMessage() { 53 | cliError.errorMessage = GetGlobalErrorMessage(fmt.Sprint(responseCode)) 54 | } 55 | } else { 56 | log.Debug("Error response parsed") 57 | cliError.apiError = &apiError 58 | if apiError.Detail != "" { 59 | log.Debug("error message set from detail field") 60 | cliError.errorMessage = apiError.Detail 61 | } else if apiError.Errors != nil && apiError.Errors[0].Detail != "" { 62 | log.Debug("error message set from nested detail field") 63 | cliError.errorMessage = apiError.Errors[0].Detail 64 | } 65 | } 66 | 67 | return &cliError 68 | } 69 | -------------------------------------------------------------------------------- /internal/logging.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | log "github.com/sirupsen/logrus" 8 | ) 9 | 10 | func InitLoggingConfig() { 11 | logPath := os.Getenv("AKAMAI_CLI_LOG_PATH") 12 | logLevel := strings.ToLower(os.Getenv("AKAMAI_LOG")) 13 | 14 | switch logLevel { 15 | case "fatal": 16 | log.SetLevel(log.FatalLevel) 17 | case "error": 18 | log.SetLevel(log.ErrorLevel) 19 | case "warn": 20 | log.SetLevel(log.WarnLevel) 21 | case "info": 22 | log.SetLevel(log.InfoLevel) 23 | case "debug": 24 | log.SetLevel(log.DebugLevel) 25 | case "trace": 26 | log.SetLevel(log.TraceLevel) 27 | default: 28 | log.SetLevel(log.FatalLevel) 29 | } 30 | 31 | if logPath != "" { 32 | file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 33 | if err == nil { 34 | log.SetOutput(file) 35 | } else { 36 | log.Warnln("Failed to log to file, using default stderr.") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/message_utils.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/oleiade/reflections" 9 | log "github.com/sirupsen/logrus" 10 | "github.com/spf13/cobra" 11 | "github.com/tidwall/gjson" 12 | ) 13 | 14 | var ( 15 | //go:embed en_US.json 16 | messageJsonBytes []byte 17 | 18 | messageJson gjson.Result 19 | rootCommandName = "akamai" 20 | jsonPathSeparator = "." 21 | flagKey = "flag" 22 | argKey = "arg" 23 | placeHolderRegex = "{{(.*?)}}" 24 | fallbackKey = "fallback" 25 | ) 26 | 27 | //Get global errors for given key 28 | func GetGlobalErrorMessage(key string) string { 29 | return getMessageForJsonPathOrFallback(strings.Join([]string{rootCommandName, Global, key}, jsonPathSeparator)) 30 | } 31 | 32 | // Return message for given key under command. 33 | func GetMessageForKey(baseCmdPath *cobra.Command, key string) string { 34 | jsonPath := getJsonPathForCommand(strings.Join([]string{baseCmdPath.CommandPath(), key}, " ")) 35 | return getMessageForJsonPathOrFallback(jsonPath) 36 | } 37 | 38 | // Get different type of message for flag 39 | func GetErrorMessageForFlag(cmd *cobra.Command, errorType, flagKeyInJson string) string { 40 | 41 | jsonPath := getJsonPathForCommand(cmd.CommandPath()) 42 | jsonPath = strings.Join([]string{jsonPath, flagKey, errorType, flagKeyInJson}, jsonPathSeparator) 43 | 44 | log.Debugf("Get message for json path [%s], error type - [%s], flag key in json - [%s]", jsonPath, errorType, flagKeyInJson) 45 | return getMessageForJsonPathOrFallback(jsonPath) 46 | } 47 | 48 | // Get different type of message for positional args 49 | func GetErrorMessageForArg(cmd *cobra.Command, errorType, argKeyInJson string) string { 50 | 51 | jsonPath := getJsonPathForCommand(cmd.CommandPath()) 52 | switch errorType { 53 | case Missing: 54 | jsonPath = strings.Join([]string{jsonPath, argKey, Missing, argKeyInJson}, jsonPathSeparator) 55 | case Invalid: 56 | jsonPath = strings.Join([]string{jsonPath, argKey, Invalid, argKeyInJson}, jsonPathSeparator) 57 | } 58 | 59 | log.Debugf("Get message for json path [%s], error type - [%s], flag key in json - [%s]", jsonPath, errorType, argKeyInJson) 60 | return getMessageForJsonPathOrFallback(jsonPath) 61 | } 62 | 63 | func GetApiErrorMessagesForCommand(cmd *cobra.Command, apiError ApiError, subResource, operation string) []string { 64 | jsonPathForCommand := getJsonPathForCommand(cmd.CommandPath()) 65 | parentErrorKey := getErrorJsonKeyForErrorType(apiError.Type) 66 | 67 | if len(apiError.Errors) != 0 { 68 | return GetApiSubErrorMessagesForCommand(cmd, apiError.Errors, parentErrorKey, subResource, operation) 69 | } else { 70 | errorPath := getJsonPathForCommand(strings.Join([]string{jsonPathForCommand, subResource, operation, parentErrorKey}, " ")) 71 | errorMessage := getMessageForJsonPathOrFallback(errorPath) 72 | return []string{getReplacedPlaceholderMessage(apiError, errorMessage)} 73 | } 74 | } 75 | 76 | // Get All the error messages for api sub errors 77 | func GetApiSubErrorMessagesForCommand(cmd *cobra.Command, apiSubError []ApiSubError, parentErrorKey, subResource, operation string) []string { 78 | jsonPathForCommand := getJsonPathForCommand(cmd.CommandPath()) 79 | var errorMessages = make([]string, len(apiSubError)) 80 | 81 | for i, subError := range apiSubError { 82 | subErrorKey := getErrorJsonKeyForErrorType(subError.Type) 83 | subErrorRequestField := getErrorJsonKeyForErrorType(subError.RequestField) 84 | log.Debug("subErrorKey", subErrorKey) 85 | errorPath := getJsonPathForCommand(strings.Join([]string{jsonPathForCommand, subResource, operation, parentErrorKey, subErrorKey, subErrorRequestField}, " ")) 86 | 87 | // /*Custom logic starts here*/ 88 | 89 | // //Pulsar object sometimes contains same error type for different objects, currently not able to figure out how to show those messages differently for different objects 90 | // //One other possible solution is show generic message 91 | // // For now this is done to only support submit test run 92 | // // First custom logic 93 | // if strings.Contains("resourceNotFound,resourceInDeletedState", subErrorKey) && checkIfMessageExist(errorPath+jsonPathSeparator+subError.RequestField+strings.Title(subErrorKey)) { 94 | 95 | // errorMessage := getMessageForJsonPathOrFallback(errorPath + jsonPathSeparator + subError.RequestField + strings.Title(subErrorKey)) 96 | // // Replace placeholder values in string from json if there are any 97 | // errorMessages[i] = getReplacedPlaceholderMessage(subError, errorMessage) 98 | // continue 99 | // } 100 | 101 | errorMessage := getMessageForJsonPathOrFallback(errorPath) 102 | // Replace placeholder values in string from json if there are any 103 | errorMessages[i] = getReplacedPlaceholderMessage(subError, errorMessage) 104 | } 105 | return errorMessages 106 | } 107 | 108 | func getErrorJsonKeyForErrorType(errorType string) string { 109 | 110 | str := strings.Split(errorType, jsonPathSeparator) 111 | var jsonPath = make([]string, len(str)) 112 | for i2, s := range str { 113 | if i2 == 0 { 114 | jsonPath[i2] = s 115 | } else { 116 | jsonPath[i2] = strings.Title(s) 117 | } 118 | } 119 | return strings.Join(jsonPath, "") 120 | } 121 | 122 | // Replace placeholder values in string from json if there are any 123 | func getReplacedPlaceholderMessage(error interface{}, errorMessage string) string { 124 | 125 | for _, str := range GetPlaceHoldersInString(errorMessage, placeHolderRegex) { 126 | value, _ := reflections.GetField(error, strings.Title(str)) 127 | errorMessage = strings.ReplaceAll(errorMessage, fmt.Sprintf("{{%s}}", str), fmt.Sprintf("%v", value)) 128 | } 129 | return errorMessage 130 | } 131 | 132 | // Return json path for given command chain, e.g. - `test-center test-suite view` converted to akamai.testCenter.testSuite.view 133 | func getJsonPathForCommand(cmdString string) string { 134 | log.Debugf("Get json path for command [%s]", cmdString) 135 | givenString := strings.Fields(cmdString) 136 | var jsonPath = make([]string, len(givenString)) 137 | 138 | for i, str := range givenString { 139 | var dashRemovedString = make([]string, len(str)) 140 | for i2, dashedString := range strings.Split(str, "-") { 141 | if i2 == 0 { 142 | dashRemovedString[i2] = dashedString 143 | } else { 144 | dashRemovedString[i2] = strings.Title(dashedString) 145 | } 146 | } 147 | jsonPath[i] = strings.Join(dashRemovedString, "") 148 | } 149 | 150 | convertedString := strings.Join(jsonPath, jsonPathSeparator) 151 | if strings.Contains(convertedString, rootCommandName) { 152 | return convertedString 153 | } 154 | 155 | return strings.Join([]string{rootCommandName, convertedString}, jsonPathSeparator) 156 | } 157 | 158 | // standard function to get message from json for given json path 159 | func checkIfMessageExist(jsonPath string) bool { 160 | message := gjson.Get(messageJson.String(), jsonPath) 161 | log.Debugf("Message for json path [%s] : [%s]", jsonPath, message.String()) 162 | return message.Exists() 163 | } 164 | 165 | // standard function to get message from json for given json path 166 | func getMessageForJsonPathOrFallback(jsonPath string) string { 167 | message := gjson.Get(messageJson.String(), jsonPath) 168 | if message.Exists() && message.Type == gjson.String { 169 | log.Debugf("Message for json path [%s] : [%s]", jsonPath, message.String()) 170 | return message.String() 171 | } else { 172 | log.Infof("Message for json path [%s] : [%s]", jsonPath, message.String()) 173 | log.Debugf("Message is not configured for jsonPath [%s]", jsonPath) 174 | return gjson.Get(messageJson.String(), rootCommandName+jsonPathSeparator+fallbackKey).String() 175 | } 176 | } 177 | 178 | // get fallback message 179 | func GetGlobalFallBackMessage() string { 180 | return gjson.Get(messageJson.String(), rootCommandName+jsonPathSeparator+fallbackKey).String() 181 | } 182 | 183 | // Initialize message file 184 | func init() { 185 | messageJson = gjson.ParseBytes(messageJsonBytes) 186 | } 187 | -------------------------------------------------------------------------------- /internal/print_diagnostics_util.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "sort" 9 | "strconv" 10 | "strings" 11 | "text/template" 12 | 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | func printJsonOutput(byt *[]byte) { 17 | var prettyJSON bytes.Buffer 18 | err := json.Indent(&prettyJSON, *byt, "", indentString) 19 | if err != nil { 20 | PrintError("Failed to indent the json response") 21 | fmt.Println("\n" + string(*byt)) 22 | return 23 | } 24 | fmt.Println("\n" + prettyJSON.String()) 25 | } 26 | 27 | func printEdgeLocationsJsonOutput(edgeLocationContainer EdgeLocationContainer, searchText string) { 28 | var edgeLocationContainerFiltered EdgeLocationContainer 29 | if searchText != "" { 30 | for _, edgeLocation := range edgeLocationContainer.EdgeLocations { 31 | if strings.Contains(strings.ToLower(edgeLocation.Id), searchText) { 32 | edgeLocationContainerFiltered.EdgeLocations = append(edgeLocationContainerFiltered.EdgeLocations, edgeLocation) 33 | } 34 | } 35 | resJson, _ := json.MarshalIndent(edgeLocationContainerFiltered, "", indentString) 36 | fmt.Println(string(resJson)) 37 | } else { 38 | resJson, _ := json.MarshalIndent(edgeLocationContainer, "", indentString) 39 | fmt.Println(string(resJson)) 40 | } 41 | } 42 | 43 | func PrintLocateIpResponse(geoLocation GeoLocation, ip string) { 44 | 45 | PrintHeader("\n" + geographicLocation + "\n") 46 | 47 | printLabelAndValue(ipAddress, ifNilReturnAlternative(ip, "-")) 48 | printLabelAndValue(countryCode, ifNilReturnAlternative(geoLocation.CountryCode, "-")) 49 | printLabelAndValue(regionCode, ifNilReturnAlternative(geoLocation.RegionCode, "-")) 50 | printLabelAndValue(city, ifNilReturnAlternative(geoLocation.City, "-")) 51 | printLabelAndValue(dma, ifNilIntPtrReturnAlternative(geoLocation.DMA, "-")) 52 | printLabelAndValue(msa, ifNilIntPtrReturnAlternative(geoLocation.MSA, "-")) 53 | printLabelAndValue(pmsa, ifNilIntPtrReturnAlternative(geoLocation.PMSA, "-")) 54 | printLabelAndValue(areaCode, ifNilReturnAlternative(geoLocation.AreaCode, "-")) 55 | printLabelAndValue(latitude, ifNilFloatPtrReturnAlternative(geoLocation.Latitude, "-")) 56 | printLabelAndValue(longitude, ifNilFloatPtrReturnAlternative(geoLocation.Longitude, "-")) 57 | printLabelAndValue(county, ifNilReturnAlternative(geoLocation.County, "-")) 58 | printLabelAndValue(continent, ifNilReturnAlternative(geoLocation.Continent, "-")) 59 | printLabelAndValue(fisp, ifNilReturnAlternative(geoLocation.FIPS, "-")) 60 | printLabelAndValue(timeZone, ifNilReturnAlternative(geoLocation.TimeZone, "-")) 61 | printLabelAndValue(zipCode, ifNilReturnAlternative(geoLocation.ZipCode, "-")) 62 | printLabelAndValue(proxy, ifNilReturnAlternative(geoLocation.Proxy, "-")) 63 | 64 | PrintHeader("\n" + networkLocation + "\n") 65 | 66 | printLabelAndValue(network, ifNilReturnAlternative(geoLocation.Network, "-")) 67 | printLabelAndValue(networkType, ifNilReturnAlternative(geoLocation.NetworkType, "-")) 68 | printLabelAndValue(asNum, ifNilIntPtrReturnAlternative(geoLocation.AsNum, "-")) 69 | printLabelAndValue(throughput, ifNilReturnAlternative(geoLocation.Throughput, "-")) 70 | 71 | } 72 | 73 | func PrintLocateIpsResponse(verifyLocateIpsResponse VerifyLocateIpsResponse, failedIpsMessage string) { 74 | var failedIps []string 75 | for _, verifyLocateIpsData := range verifyLocateIpsResponse.Result { 76 | if verifyLocateIpsData.ExecutionStatus == "SUCCESS" { 77 | PrintLocateIpResponse(verifyLocateIpsData.GeoLocation, verifyLocateIpsData.IpAddress) 78 | } else { 79 | failedIps = append(failedIps, verifyLocateIpsData.IpAddress) 80 | } 81 | } 82 | if len(failedIps) > 0 { 83 | PrintFailedIps(failedIps, failedIpsMessage) 84 | } 85 | } 86 | 87 | func ifNilReturnAlternative(og, alternate interface{}) interface{} { 88 | if og != "" { 89 | return og 90 | } 91 | return alternate 92 | } 93 | 94 | func ifNilIntPtrReturnAlternative(og *int, alternate interface{}) interface{} { 95 | if og == nil { 96 | return alternate 97 | } 98 | return *og 99 | } 100 | 101 | func ifNilFloatPtrReturnAlternative(og *float32, alternate interface{}) interface{} { 102 | if og == nil { 103 | return alternate 104 | } 105 | return *og 106 | } 107 | 108 | func PrintVerifyIpResponse(verifyLocateIpResponse VerifyLocateIpResponse) { 109 | 110 | funcMap := template.FuncMap{ 111 | "bold": bold, 112 | } 113 | var tmp string 114 | if verifyLocateIpResponse.Result.IsEdgeIp { 115 | tmp = isEdgeIpTemplate 116 | } else { 117 | tmp = isNotEdgeIpTemplate 118 | } 119 | printTemplate(funcMap, tmp, verifyLocateIpResponse.Request) 120 | 121 | } 122 | 123 | func PrintVerifyIpsResponse(verifyLocateIpsResponse VerifyLocateIpsResponse, failedIpsMessage string) { 124 | 125 | funcMap := template.FuncMap{ 126 | "bold": bold, 127 | } 128 | var tmp string 129 | var failedIps []string 130 | for _, verifyLocateIpsData := range verifyLocateIpsResponse.Result { 131 | if verifyLocateIpsData.ExecutionStatus == "SUCCESS" { 132 | if verifyLocateIpsData.IsEdgeIp { 133 | tmp = isEdgeIpTemplate 134 | } else { 135 | tmp = isNotEdgeIpTemplate 136 | } 137 | } else { 138 | failedIps = append(failedIps, verifyLocateIpsData.IpAddress) 139 | } 140 | printTemplate(funcMap, tmp, verifyLocateIpsData) 141 | } 142 | if len(failedIps) > 0 { 143 | PrintFailedIps(failedIps, failedIpsMessage) 144 | } 145 | 146 | } 147 | 148 | func PrintFailedIps(failedIps []string, failedIpsMessage string) { 149 | fmt.Println("\n" + failedIpsMessage) 150 | for _, failedIp := range failedIps { 151 | fmt.Println(failedIp) 152 | } 153 | } 154 | 155 | func PrintDigResponse(digResponse DigResponse) { 156 | funcMap := template.FuncMap{ 157 | "header": header, 158 | "bold": bold, 159 | "locationToString": EdgeIpLocation.toString, 160 | "dateToString": isoToDate, 161 | } 162 | 163 | printTemplate(funcMap, digTemplate, digResponse) 164 | PrintSuggestedActions(digResponse.SuggestedActions) 165 | } 166 | 167 | func PrintUserDiagnosticsDataGroupDetailsAfterCreate(userDiagnosticsDataGroupDetails UserDiagnosticsDataGroupDetails) { 168 | 169 | // standard functions for templates 170 | funcMap := template.FuncMap{ 171 | "bold": bold, 172 | } 173 | 174 | printTemplate(funcMap, userDiagnosticsCreateTemplate, userDiagnosticsDataGroupDetails) 175 | } 176 | 177 | func PrintUserDiagnosticsDataGroupDetailsTable(groupsList []UserDiagnosticsDataGroupDetails) { 178 | 179 | if len(groupsList) <= 0 { 180 | PrintWarning("No diagnostic links found") 181 | fmt.Println() 182 | return 183 | } 184 | 185 | //as createdTime is in ISO format, normal string comparison suffices 186 | sort.SliceStable(groupsList, func(i, j int) bool { 187 | return groupsList[i].CreatedTime > groupsList[j].CreatedTime 188 | }) 189 | 190 | tableHeaders := []string{linkId, hostNameOrUrl, statusUserDiagnosticsLink, diagnosticLink, results, user, requestDate} 191 | content := make([][]string, len(groupsList)) 192 | 193 | for i, grp := range groupsList { 194 | 195 | log.Debug("created time string: ", grp.CreatedTime) 196 | convertedTime := isoToDate(grp.CreatedTime) 197 | urlRow := grp.URL 198 | if grp.URL == "" { 199 | urlRow = grp.IpaHostname 200 | } 201 | grp.DiagnosticLinkStatus = CapsToTitle(grp.DiagnosticLinkStatus) 202 | if grp.DiagnosticLinkStatus == "Active" { 203 | grp.DiagnosticLinkStatus = success(grp.DiagnosticLinkStatus) 204 | } 205 | row := []string{grp.GroupID, urlRow, grp.DiagnosticLinkStatus, grp.DiagnosticLink, strconv.Itoa(grp.RecordCount), grp.CreatedBy, convertedTime} 206 | content[i] = row 207 | } 208 | 209 | fmt.Println("\n" + userDiagnosticsListNote + "\n") 210 | ShowTable(tableHeaders, content) 211 | } 212 | 213 | func PrintUserDiagnosticsDataGroupDetails(userDiagnosticsDataGroupDetails UserDiagnosticsDataGroupDetails) { 214 | 215 | // standard functions for templates 216 | funcMap := template.FuncMap{ 217 | "bold": bold, 218 | "header": header, 219 | "italic": italic, 220 | "capsToTitle": CapsToTitle, 221 | } 222 | 223 | printTemplate(funcMap, userDiagnosticsGetTemplate, userDiagnosticsDataGroupDetails) 224 | 225 | } 226 | 227 | func PrintTranslateUrlResponse(arlContainer ArlContainer) { 228 | 229 | funcMap := template.FuncMap{ 230 | "bold": bold, 231 | "header": header, 232 | } 233 | printTemplate(funcMap, translateUrlTemplate, arlContainer) 234 | } 235 | func printEdgeLocations(edgeLocationContainer EdgeLocationContainer, searchText string) { 236 | PrintHeader("\n" + edgeLocations + "\n") 237 | count := 0 238 | if searchText != "" { 239 | for _, edgeLocation := range edgeLocationContainer.EdgeLocations { 240 | if strings.Contains(strings.ToLower(edgeLocation.Id), searchText) { 241 | printLabelAndValue(edgeLocation.Value, edgeLocation.Id) 242 | count++ 243 | } 244 | } 245 | } else { 246 | for _, edgeLocation := range edgeLocationContainer.EdgeLocations { 247 | printLabelAndValue(edgeLocation.Value, edgeLocation.Id) 248 | count++ 249 | } 250 | } 251 | if count == 0 { 252 | PrintWarning("No edge locations found\n") 253 | } 254 | } 255 | 256 | func PrintIpaHostnamesTable(ipaHostnamesResponse IpaHostnameResponse) { 257 | tableHeaders := []string{ipaHostname} 258 | content := make([][]string, len(ipaHostnamesResponse.Hostnames)) 259 | 260 | for i, hostname := range ipaHostnamesResponse.Hostnames { 261 | row := []string{hostname} 262 | content[i] = row 263 | } 264 | 265 | PrintHeader("\n" + ipaHostnames + "\n") 266 | ShowTable(tableHeaders, content) 267 | } 268 | 269 | func PrintGtmHostnamesTable(gtmPropertyContainer GtmPropertyContainer) { 270 | tableHeaders := []string{gtmHostname} 271 | content := make([][]string, len(gtmPropertyContainer.GtmProperties)) 272 | 273 | for i, gtmProperties := range gtmPropertyContainer.GtmProperties { 274 | row := []string{gtmProperties.Hostname} 275 | content[i] = row 276 | } 277 | 278 | PrintHeader("\n" + gtmHostnames + "\n") 279 | ShowTable(tableHeaders, content) 280 | } 281 | 282 | func PrintGtmTestTargetIpTable(gtmPropertyIpsContainer GtmPropertyIpsContainer) { 283 | tableHeaders := []string{gtmHostname, testIp, target} 284 | content := make([][]string, 1) 285 | 286 | var testIpConcat string 287 | var targetConcat string 288 | 289 | for _, testIp := range gtmPropertyIpsContainer.GTMPropertyIps.TestIps { 290 | testIpConcat = testIpConcat + testIp + "\n" 291 | } 292 | 293 | for _, target := range gtmPropertyIpsContainer.GTMPropertyIps.Targets { 294 | targetConcat = targetConcat + target + "\n" 295 | } 296 | 297 | row := []string{gtmPropertyIpsContainer.GTMPropertyIps.Property + "." + gtmPropertyIpsContainer.GTMPropertyIps.Domain, testIpConcat, targetConcat} 298 | content[0] = row 299 | 300 | PrintHeader("\n" + gtmHostnames + "\n") 301 | ShowTable(tableHeaders, content) 302 | } 303 | 304 | func PrintCurlResponse(curlResponse CurlResponse) { 305 | funcMap := template.FuncMap{ 306 | "header": header, 307 | "bold": bold, 308 | "italic": italic, 309 | "capsToTitle": CapsToTitle, 310 | "locationToString": EdgeIpLocation.toString, 311 | "splitHeaderValue": SplitCurlHeaderString, 312 | "isoDate": isoToDate, 313 | } 314 | 315 | printTemplate(funcMap, curlTemplate, curlResponse) 316 | PrintSuggestedActions(curlResponse.SuggestedActions) 317 | } 318 | 319 | func PrintMtrResponse(mtrRequest MtrRequest, mtrResponse MtrResponse) { 320 | funcMap := template.FuncMap{ 321 | "header": header, 322 | "bold": bold, 323 | "locationToString": EdgeIpLocation.toString, 324 | "dateToString": isoToDate, 325 | } 326 | 327 | if net.ParseIP(mtrRequest.Destination) != nil { 328 | mtrResponse.DestinationInternalIP = mtrRequest.Destination 329 | } 330 | printTemplate(funcMap, mtrDetailsTemplate, mtrResponse) 331 | 332 | tableHeaders := strings.Split("#, Host:"+mtrResponse.Result.Host+", "+mtrTableCol, ",") 333 | content := make([][]string, len(mtrResponse.Result.Hops)) 334 | 335 | for i, hop := range mtrResponse.Result.Hops { 336 | hostAndIP := hop.Host 337 | if hop.Host == "" { 338 | hostAndIP = "???" 339 | } 340 | if hop.IP != "" { 341 | hostAndIP += "\n" + hop.IP 342 | } 343 | content[i] = []string{fmt.Sprint(hop.Number), hostAndIP, fmt.Sprint(hop.PacketLoss), fmt.Sprint(hop.SentPackets), fmt.Sprint(hop.LastPacketLatency), fmt.Sprint(hop.AverageLatency), fmt.Sprint(hop.BestRtt), fmt.Sprint(hop.WorstRtt), fmt.Sprint(hop.StandardDeviation), hop.IPLocation.toStringNoBrackets()} 344 | } 345 | ShowTable(tableHeaders, content) 346 | PrintSuggestedActions(mtrResponse.SuggestedActions) 347 | } 348 | 349 | func PrintSuggestedActions(suggestedActions []string) { 350 | if suggestedActions != nil { 351 | PrintHeader("Suggested Actions\n") 352 | for _, suggestedAction := range suggestedActions { 353 | fmt.Println(suggestedAction) 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /internal/print_utils.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | //This class will have only akamai/global cli standard print functions 4 | import ( 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | "text/template" 10 | 11 | "github.com/fatih/color" 12 | "github.com/olekukonko/tablewriter" 13 | log "github.com/sirupsen/logrus" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | // Start Standard Print Functions 18 | func PrintError(message string, args ...interface{}) { 19 | c := color.New(color.FgRed) 20 | c.Fprintf(os.Stderr, message, args...) 21 | } 22 | 23 | func PrintWarning(message string, args ...interface{}) { 24 | c := color.New(color.FgCyan) 25 | c.Fprintf(os.Stderr, message, args...) 26 | } 27 | 28 | func PrintHeader(message string, args ...interface{}) { 29 | c := color.New(color.FgYellow).Add(color.Bold) 30 | c.Printf(message, args...) 31 | } 32 | 33 | func PrintSuccess(message string, args ...interface{}) { 34 | c := color.New(color.FgGreen) 35 | c.Printf(message, args...) 36 | } 37 | 38 | func success(a ...interface{}) string { 39 | c := color.New(color.FgGreen) 40 | return c.Sprint(a...) 41 | } 42 | 43 | func blue(a ...interface{}) string { 44 | c := color.New(color.FgBlue) 45 | return c.Sprint(a...) 46 | } 47 | 48 | func bold(a ...interface{}) string { 49 | c := color.New(color.Bold) 50 | return c.Sprint(a...) 51 | } 52 | 53 | func italic(a ...interface{}) string { 54 | c := color.New(color.Italic) 55 | return c.Sprint(a...) 56 | } 57 | 58 | func header(a ...interface{}) string { 59 | c := color.New(color.FgYellow).Add(color.Bold) 60 | return c.Sprint(a...) 61 | } 62 | 63 | func printLabelAndValue(label string, value interface{}) { 64 | c := color.New(color.Bold) 65 | c.Printf(label + ": ") 66 | fmt.Printf("%v\n", value) 67 | } 68 | 69 | func Abort(message string, errorCode int) { 70 | PrintError(message + "\n") 71 | os.Exit(errorCode) 72 | } 73 | 74 | func AbortWithUsageAndMessage(cmd *cobra.Command, message string, errorCode int) { 75 | PrintError(message + "\n\n") 76 | err := cmd.Usage() 77 | if err != nil { 78 | return 79 | } 80 | os.Exit(errorCode) 81 | } 82 | func AbortForCommand(cmd *cobra.Command, cliError *CliError) { 83 | AbortForCommandWithSubResource(cmd, cliError, Empty, Empty) 84 | } 85 | 86 | func AbortForCommandWithSubResource(cmd *cobra.Command, cliError *CliError, subResource, operation string) { 87 | 88 | responseCode := strconv.Itoa(cliError.responseCode) 89 | if len(responseCode) == 3 && strings.Contains("500,502,503,504", responseCode) { 90 | PrintError(GetGlobalErrorMessage(responseCode) + "\n\n") 91 | } else if cliError.apiError != nil { 92 | printErrorMessages(GetApiErrorMessagesForCommand(cmd, *cliError.apiError, subResource, operation)) 93 | println() 94 | } else if len(cliError.apiSubErrors) != 0 { 95 | printErrorMessages(GetApiSubErrorMessagesForCommand(cmd, cliError.apiSubErrors, "", subResource, operation)) 96 | println() 97 | } else { 98 | PrintError(cliError.errorMessage + "\n\n") 99 | } 100 | 101 | os.Exit(1) 102 | } 103 | 104 | func printErrorMessages(errorMessages []string) { 105 | for _, message := range errorMessages { 106 | PrintError(message + "\n") 107 | } 108 | } 109 | 110 | // ShowTable Standard function to show table in same format across Test Center CLI 111 | func ShowTable(tableHeaders []string, tableContents [][]string) { 112 | table := tablewriter.NewWriter(os.Stdout) 113 | table.SetRowLine(true) 114 | 115 | table.SetHeader(tableHeaders) 116 | table.SetAutoFormatHeaders(false) 117 | table.AppendBulk(tableContents) 118 | table.Render() 119 | fmt.Printf("\nTotal items: %d\n", len(tableContents)) 120 | } 121 | 122 | func printTemplate(funcMap template.FuncMap, templateToParse string, data interface{}) { 123 | 124 | tmp, err := template.New("template").Funcs(funcMap).Parse(templateToParse) 125 | 126 | // if there is error, 127 | if err != nil { 128 | log.Error(err) 129 | PrintError("Output can not shown because of internal cli error.\n") 130 | os.Exit(1) 131 | } else { 132 | // standard output to print merged data 133 | err = tmp.Execute(os.Stdout, data) 134 | println() 135 | if err != nil { 136 | log.Error(err) 137 | PrintError("Output can not shown because of internal cli error.\n") 138 | os.Exit(1) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /internal/service.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | log "github.com/sirupsen/logrus" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | type Service struct { 12 | api ApiClient 13 | cmd *cobra.Command 14 | outputAsJson bool 15 | } 16 | 17 | func NewService(api ApiClient, cmd *cobra.Command, outputAsJson bool) *Service { 18 | return &Service{api, cmd, outputAsJson} 19 | } 20 | 21 | func (svc Service) LocateIp(locateIpsRequest VerifyLocateIpsRequest) { 22 | 23 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 24 | 25 | byt, err := svc.api.LocateIp(locateIpsRequest) 26 | if err != nil { 27 | StopSpinner(spinner, false) 28 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 29 | } 30 | 31 | if svc.outputAsJson { 32 | StopSpinner(spinner, true) 33 | printJsonOutput(byt) 34 | return 35 | } 36 | 37 | var locateIpsResponse VerifyLocateIpsResponse 38 | unmarshalErr := json.Unmarshal(*byt, &locateIpsResponse) 39 | if unmarshalErr != nil { 40 | StopSpinner(spinner, false) 41 | log.Error(err) 42 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 43 | } 44 | 45 | StopSpinner(spinner, true) 46 | PrintLocateIpsResponse(locateIpsResponse, GetMessageForKey(svc.cmd, FailedIps)) 47 | 48 | } 49 | 50 | func (svc Service) VerifyIp(verifyIpsRequest VerifyLocateIpsRequest) { 51 | 52 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 53 | 54 | byt, err := svc.api.VerifyIp(verifyIpsRequest) 55 | if err != nil { 56 | StopSpinner(spinner, false) 57 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 58 | } 59 | 60 | if svc.outputAsJson { 61 | StopSpinner(spinner, true) 62 | printJsonOutput(byt) 63 | return 64 | } 65 | 66 | var verifyIpsResponse VerifyLocateIpsResponse 67 | unmarshalErr := json.Unmarshal(*byt, &verifyIpsResponse) 68 | if unmarshalErr != nil { 69 | StopSpinner(spinner, false) 70 | log.Error(unmarshalErr) 71 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 72 | } 73 | 74 | StopSpinner(spinner, true) 75 | PrintVerifyIpsResponse(verifyIpsResponse, GetMessageForKey(svc.cmd, FailedIps)) 76 | 77 | } 78 | 79 | func (svc Service) VerifyLocateIp(verifyLocateIpRequest VerifyLocateIpRequest) { 80 | 81 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 82 | 83 | byt, err := svc.api.VerifyLocateIp(verifyLocateIpRequest) 84 | if err != nil { 85 | StopSpinner(spinner, false) 86 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 87 | } 88 | 89 | if svc.outputAsJson { 90 | StopSpinner(spinner, true) 91 | printJsonOutput(byt) 92 | return 93 | } 94 | 95 | var verifyLocateIpResponse VerifyLocateIpResponse 96 | unmarshalErr := json.Unmarshal(*byt, &verifyLocateIpResponse) 97 | if unmarshalErr != nil { 98 | StopSpinner(spinner, false) 99 | log.Error(unmarshalErr) 100 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 101 | } 102 | 103 | StopSpinner(spinner, true) 104 | PrintVerifyIpResponse(verifyLocateIpResponse) 105 | PrintLocateIpResponse(verifyLocateIpResponse.Result.GeoLocation, verifyLocateIpResponse.Request.IpAddress) 106 | 107 | } 108 | 109 | func (svc Service) Dig(digRequest DigRequest) { 110 | 111 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 112 | 113 | byt, err := svc.api.Dig(digRequest) 114 | if err != nil { 115 | StopSpinner(spinner, false) 116 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 117 | } 118 | 119 | if svc.outputAsJson { 120 | StopSpinner(spinner, true) 121 | printJsonOutput(byt) 122 | return 123 | } 124 | 125 | var digResponse DigResponse 126 | if json.Unmarshal(*byt, &digResponse) != nil { 127 | StopSpinner(spinner, false) 128 | log.Error(err) 129 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 130 | } 131 | 132 | StopSpinner(spinner, true) 133 | PrintDigResponse(digResponse) 134 | } 135 | 136 | func (svc Service) UserDiagnosticsCreate(userDiagnosticsDataRequest UserDiagnosticsDataRequest) { 137 | 138 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 139 | 140 | byt, err := svc.api.UserDiagnosticsCreate(userDiagnosticsDataRequest) 141 | if err != nil { 142 | StopSpinner(spinner, false) 143 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 144 | } 145 | 146 | if svc.outputAsJson { 147 | StopSpinner(spinner, true) 148 | printJsonOutput(byt) 149 | return 150 | } 151 | 152 | var userDiagnosticsDataGroupDetails UserDiagnosticsDataGroupDetails 153 | unmarshalErr := json.Unmarshal(*byt, &userDiagnosticsDataGroupDetails) 154 | if unmarshalErr != nil { 155 | StopSpinner(spinner, false) 156 | log.Error(unmarshalErr) 157 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 158 | } 159 | 160 | StopSpinner(spinner, true) 161 | PrintUserDiagnosticsDataGroupDetailsAfterCreate(userDiagnosticsDataGroupDetails) 162 | } 163 | 164 | func (svc Service) UserDiagnosticsList(url, user string, active bool) { 165 | 166 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 167 | 168 | byt, err := svc.api.UserDiagnosticsList(url, user, active) 169 | if err != nil { 170 | StopSpinner(spinner, false) 171 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 172 | } 173 | 174 | if svc.outputAsJson { 175 | StopSpinner(spinner, true) 176 | printJsonOutput(byt) 177 | return 178 | } 179 | 180 | var userDiagnosticsDataGroupDetailsList ListResponse 181 | unmarshalErr := json.Unmarshal(*byt, &userDiagnosticsDataGroupDetailsList) 182 | if unmarshalErr != nil { 183 | StopSpinner(spinner, false) 184 | log.Error(unmarshalErr) 185 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 186 | } 187 | 188 | StopSpinner(spinner, true) 189 | PrintUserDiagnosticsDataGroupDetailsTable(userDiagnosticsDataGroupDetailsList.Groups) 190 | } 191 | 192 | func (svc Service) UserDiagnosticsGet(linkId string, mtr, dig, curl bool) { 193 | 194 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 195 | 196 | byt, err := svc.api.UserDiagnosticsGet(linkId, mtr, dig, curl) 197 | if err != nil { 198 | StopSpinner(spinner, false) 199 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 200 | } 201 | 202 | if svc.outputAsJson { 203 | StopSpinner(spinner, true) 204 | printJsonOutput(byt) 205 | return 206 | } 207 | 208 | var userDiagnosticsDataGroupDetails UserDiagnosticsDataGroupDetails 209 | unmarshalErr := json.Unmarshal(*byt, &userDiagnosticsDataGroupDetails) 210 | if unmarshalErr != nil { 211 | StopSpinner(spinner, false) 212 | log.Error(unmarshalErr) 213 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 214 | } 215 | 216 | StopSpinner(spinner, true) 217 | PrintUserDiagnosticsDataGroupDetails(userDiagnosticsDataGroupDetails) 218 | } 219 | 220 | func (svc Service) TranslateUrl(arlRequest ArlRequest) { 221 | 222 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 223 | 224 | byt, err := svc.api.TranslateUrl(arlRequest) 225 | if err != nil { 226 | StopSpinner(spinner, false) 227 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 228 | } 229 | 230 | if svc.outputAsJson { 231 | StopSpinner(spinner, true) 232 | printJsonOutput(byt) 233 | return 234 | } 235 | 236 | var arlContainer ArlContainer 237 | unmarshalErr := json.Unmarshal(*byt, &arlContainer) 238 | 239 | if unmarshalErr != nil { 240 | StopSpinner(spinner, false) 241 | log.Error(err) 242 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 243 | } 244 | StopSpinner(spinner, true) 245 | PrintTranslateUrlResponse(arlContainer) 246 | } 247 | 248 | func (svc Service) TranslateError(errorTranslatorRequest ErrorTranslatorRequest) { 249 | 250 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 251 | 252 | byt, err := svc.api.TranslateErrorPost(errorTranslatorRequest) 253 | if err != nil { 254 | StopSpinner(spinner, false) 255 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 256 | } 257 | var errorTranslatorResponse ErrorTranslatorResponse 258 | unmarshalErr := json.Unmarshal(*byt, &errorTranslatorResponse) 259 | 260 | if unmarshalErr != nil { 261 | StopSpinner(spinner, false) 262 | log.Error(unmarshalErr) 263 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 264 | } 265 | 266 | var excecutionStatus string 267 | 268 | for { 269 | 270 | log.Debug("Waiting for ", errorTranslatorResponse.RetryAfter, " seconds\n") 271 | time.Sleep(time.Duration(errorTranslatorResponse.RetryAfter) * time.Second) 272 | 273 | log.Debug("Checking Execution status..") 274 | byt, err := svc.api.TranslateErrorGet(errorTranslatorResponse.Link) 275 | if err != nil { 276 | StopSpinner(spinner, false) 277 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 278 | } 279 | 280 | unmarshalErr := json.Unmarshal(*byt, &errorTranslatorResponse) 281 | 282 | log.Debug("status recieved: ", errorTranslatorResponse.ExecutionStatus) 283 | excecutionStatus = errorTranslatorResponse.ExecutionStatus 284 | 285 | if unmarshalErr != nil { 286 | StopSpinner(spinner, false) 287 | log.Error(err) 288 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 289 | } 290 | 291 | if excecutionStatus != "IN_PROGRESS" { 292 | if excecutionStatus == "FAILURE" { 293 | StopSpinner(spinner, false) 294 | } else { 295 | StopSpinner(spinner, true) 296 | } 297 | printJsonOutput(byt) 298 | return 299 | } 300 | } 301 | } 302 | 303 | func (svc Service) EdgeLocations(searchText string) { 304 | 305 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 306 | 307 | byt, err := svc.api.EdgeLocations() 308 | 309 | if err != nil { 310 | StopSpinner(spinner, false) 311 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 312 | } 313 | 314 | var edgeLocationContainer EdgeLocationContainer 315 | unmarshalErr := json.Unmarshal(*byt, &edgeLocationContainer) 316 | if unmarshalErr != nil { 317 | StopSpinner(spinner, false) 318 | log.Error(err) 319 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 320 | } 321 | 322 | StopSpinner(spinner, true) 323 | 324 | if svc.outputAsJson { 325 | printEdgeLocationsJsonOutput(edgeLocationContainer, searchText) 326 | return 327 | } 328 | 329 | printEdgeLocations(edgeLocationContainer, searchText) 330 | 331 | } 332 | 333 | func (svc Service) IpaHostnames() { 334 | 335 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 336 | 337 | byt, err := svc.api.IpaHostnames() 338 | 339 | if err != nil { 340 | StopSpinner(spinner, false) 341 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 342 | } 343 | 344 | if svc.outputAsJson { 345 | StopSpinner(spinner, true) 346 | printJsonOutput(byt) 347 | return 348 | } 349 | 350 | var ipaHostnameResponse IpaHostnameResponse 351 | unmarshalErr := json.Unmarshal(*byt, &ipaHostnameResponse) 352 | if unmarshalErr != nil { 353 | StopSpinner(spinner, false) 354 | log.Error(err) 355 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 356 | } 357 | 358 | StopSpinner(spinner, true) 359 | 360 | PrintIpaHostnamesTable(ipaHostnameResponse) 361 | 362 | } 363 | 364 | func (svc Service) UrlHealthCheck(urlHealthCheckRequest UrlHealthCheckRequest) { 365 | 366 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 367 | 368 | byt, err := svc.api.UrlHealthCheckPost(urlHealthCheckRequest) 369 | if err != nil { 370 | StopSpinner(spinner, false) 371 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 372 | } 373 | var urlHealthCheckResponse UrlHealthCheckResponse 374 | unmarshalErr := json.Unmarshal(*byt, &urlHealthCheckResponse) 375 | 376 | if unmarshalErr != nil { 377 | StopSpinner(spinner, false) 378 | log.Error(unmarshalErr) 379 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 380 | } 381 | 382 | var excecutionStatus string 383 | 384 | for i := 0; i < 6; i++ { 385 | 386 | log.Debug("Waiting for ", urlHealthCheckResponse.RetryAfter, " seconds\n") 387 | time.Sleep(time.Duration(urlHealthCheckResponse.RetryAfter) * time.Second) 388 | 389 | log.Debug("Checking Execution status..") 390 | byt, err := svc.api.UrlHealthCheckGet(urlHealthCheckResponse.Link) 391 | if err != nil { 392 | StopSpinner(spinner, false) 393 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 394 | } 395 | 396 | unmarshalErr := json.Unmarshal(*byt, &urlHealthCheckResponse) 397 | 398 | log.Debug("status recieved: ", urlHealthCheckResponse.ExecutionStatus) 399 | excecutionStatus = urlHealthCheckResponse.ExecutionStatus 400 | 401 | if unmarshalErr != nil { 402 | StopSpinner(spinner, false) 403 | log.Error(unmarshalErr) 404 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 405 | } 406 | 407 | if excecutionStatus != "IN_PROGRESS" { 408 | if excecutionStatus == "FAILURE" { 409 | StopSpinner(spinner, false) 410 | } else { 411 | StopSpinner(spinner, true) 412 | } 413 | printJsonOutput(byt) 414 | return 415 | } 416 | } 417 | StopSpinner(spinner, false) 418 | Abort(GetGlobalErrorMessage("500"), 500-HttpCodeExitCodeDiff) 419 | } 420 | 421 | func (svc Service) GtmHostnames() { 422 | 423 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 424 | 425 | byt, err := svc.api.GtmHostnames() 426 | 427 | if err != nil { 428 | StopSpinner(spinner, false) 429 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 430 | } 431 | 432 | if svc.outputAsJson { 433 | StopSpinner(spinner, true) 434 | printJsonOutput(byt) 435 | return 436 | } 437 | 438 | var gtmPropertyContainer GtmPropertyContainer 439 | 440 | unmarshalErr := json.Unmarshal(*byt, >mPropertyContainer) 441 | if unmarshalErr != nil { 442 | StopSpinner(spinner, false) 443 | log.Error(err) 444 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 445 | } 446 | 447 | StopSpinner(spinner, true) 448 | 449 | PrintGtmHostnamesTable(gtmPropertyContainer) 450 | 451 | } 452 | 453 | func (svc Service) GtmTestTargetIp(hostname string) { 454 | 455 | spinner := StartSpinner(GetMessageForKey(svc.cmd, "spinnerMessageTestTargetIp")) 456 | 457 | byt, err := svc.api.GtmHostnames() 458 | 459 | if err != nil { 460 | StopSpinner(spinner, false) 461 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 462 | } 463 | 464 | var gtmPropertyContainer GtmPropertyContainer 465 | 466 | unmarshalErr := json.Unmarshal(*byt, >mPropertyContainer) 467 | if unmarshalErr != nil { 468 | StopSpinner(spinner, false) 469 | log.Error(err) 470 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 471 | } 472 | 473 | log.Debug("Got GtmHostnames response") 474 | 475 | for _, gtmProperty := range gtmPropertyContainer.GtmProperties { 476 | if gtmProperty.Property+"."+gtmProperty.Domain == hostname { 477 | byt, err := svc.api.GtmTestTargetIp(gtmProperty.Property, gtmProperty.Domain) 478 | 479 | if err != nil { 480 | StopSpinner(spinner, false) 481 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 482 | } 483 | 484 | if svc.outputAsJson { 485 | StopSpinner(spinner, true) 486 | printJsonOutput(byt) 487 | return 488 | } 489 | 490 | var gtmPropertyIpsContainer GtmPropertyIpsContainer 491 | 492 | unmarshalErr := json.Unmarshal(*byt, >mPropertyIpsContainer) 493 | if unmarshalErr != nil { 494 | StopSpinner(spinner, false) 495 | log.Error(err) 496 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 497 | } 498 | 499 | StopSpinner(spinner, true) 500 | 501 | PrintGtmTestTargetIpTable(gtmPropertyIpsContainer) 502 | return 503 | } 504 | } 505 | 506 | StopSpinner(spinner, true) 507 | PrintWarning("\n" + hostname + " is not a valid gtm hostname \n") 508 | 509 | } 510 | 511 | func (svc Service) Curl(curlRequest CurlRequest) { 512 | 513 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 514 | 515 | byt, err := svc.api.Curl(curlRequest) 516 | if err != nil { 517 | StopSpinner(spinner, false) 518 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 519 | } 520 | 521 | if svc.outputAsJson { 522 | StopSpinner(spinner, true) 523 | printJsonOutput(byt) 524 | return 525 | } 526 | 527 | var curlResponse CurlResponse 528 | unmarshalErr := json.Unmarshal(*byt, &curlResponse) 529 | 530 | if unmarshalErr != nil { 531 | StopSpinner(spinner, false) 532 | log.Error(unmarshalErr) 533 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 534 | } 535 | 536 | StopSpinner(spinner, true) 537 | PrintCurlResponse(curlResponse) 538 | } 539 | 540 | func (svc Service) Estats(estatsRequest EstatsRequest, logsEstatsFlag bool) { 541 | 542 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 543 | 544 | byt, err := svc.api.Estats(estatsRequest) 545 | if err != nil { 546 | StopSpinner(spinner, false) 547 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 548 | } 549 | 550 | var estatsResult EstatsResult 551 | unmarshalErr := json.Unmarshal(*byt, &estatsResult) 552 | 553 | if unmarshalErr != nil { 554 | StopSpinner(spinner, false) 555 | log.Error(unmarshalErr) 556 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 557 | } 558 | 559 | var estatsResultWrapper EstatsResultWrapper 560 | estatsResultWrapper.EstatsResult = estatsResult 561 | 562 | var grepResponses []EstatsGrepResponse 563 | 564 | if logsEstatsFlag { 565 | if len(estatsResult.Result.TopEdgeIpsWithError) > 0 { 566 | var edgeIpInfo = estatsResult.Result.TopEdgeIpsWithError[0] 567 | byt1, err1 := svc.api.GrepGet(edgeIpInfo.EdgeLogsLink) 568 | 569 | if err1 != nil { 570 | StopSpinner(spinner, false) 571 | Abort(err1.errorMessage, err1.responseCode-HttpCodeExitCodeDiff) 572 | } 573 | var estatsGrepResponse1 EstatsGrepResponse 574 | unmarshalErr := json.Unmarshal(*byt1, &estatsGrepResponse1) 575 | 576 | if unmarshalErr != nil { 577 | StopSpinner(spinner, false) 578 | log.Error(unmarshalErr) 579 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 580 | } 581 | grepResponses = append(grepResponses, estatsGrepResponse1) 582 | } 583 | if len(estatsResult.Result.TopEdgeIpsWithErrorFromOrigin) > 0 { 584 | var edgeIpInfo = estatsResult.Result.TopEdgeIpsWithErrorFromOrigin[0] 585 | byt1, err1 := svc.api.GrepGet(edgeIpInfo.EdgeLogsLink) 586 | 587 | if err1 != nil { 588 | StopSpinner(spinner, false) 589 | Abort(err1.errorMessage, err1.responseCode-HttpCodeExitCodeDiff) 590 | } 591 | var estatsGrepResponse1 EstatsGrepResponse 592 | unmarshalErr := json.Unmarshal(*byt1, &estatsGrepResponse1) 593 | 594 | if unmarshalErr != nil { 595 | StopSpinner(spinner, false) 596 | log.Error(unmarshalErr) 597 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 598 | } 599 | grepResponses = append(grepResponses, estatsGrepResponse1) 600 | } 601 | estatsResultWrapper.EstatsLogLines = grepResponses 602 | } 603 | 604 | StopSpinner(spinner, true) 605 | newFsConfigBytes, _ := json.Marshal(estatsResultWrapper) 606 | printJsonOutput(&newFsConfigBytes) 607 | 608 | } 609 | 610 | func (svc Service) Mtr(mtrRequest MtrRequest) { 611 | 612 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 613 | 614 | byt, err := svc.api.Mtr(mtrRequest) 615 | if err != nil { 616 | StopSpinner(spinner, false) 617 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 618 | } 619 | 620 | if svc.outputAsJson { 621 | StopSpinner(spinner, true) 622 | printJsonOutput(byt) 623 | return 624 | } 625 | 626 | var mtrResponse MtrResponse 627 | unmarshalErr := json.Unmarshal(*byt, &mtrResponse) 628 | if unmarshalErr != nil { 629 | StopSpinner(spinner, false) 630 | log.Error(unmarshalErr) 631 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 632 | } 633 | 634 | StopSpinner(spinner, true) 635 | PrintMtrResponse(mtrRequest, mtrResponse) 636 | } 637 | 638 | func (svc Service) Grep(grepRequest GrepRequest) { 639 | 640 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 641 | 642 | byt, err := svc.api.GrepPost(grepRequest) 643 | if err != nil { 644 | StopSpinner(spinner, false) 645 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 646 | } 647 | var grepResponse GrepResponse 648 | unmarshalErr := json.Unmarshal(*byt, &grepResponse) 649 | 650 | if unmarshalErr != nil { 651 | StopSpinner(spinner, false) 652 | log.Error(err) 653 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 654 | } 655 | 656 | var excecutionStatus string 657 | 658 | for i := 0; i < 40; i++ { 659 | 660 | log.Debug("Waiting for ", grepResponse.RetryAfter, " seconds\n") 661 | time.Sleep(time.Duration(grepResponse.RetryAfter) * time.Second) 662 | 663 | log.Debug("Checking Execution status..") 664 | byt, err := svc.api.GrepGet(grepResponse.Link) 665 | if err != nil { 666 | StopSpinner(spinner, false) 667 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 668 | } 669 | 670 | unmarshalErr := json.Unmarshal(*byt, &grepResponse) 671 | 672 | log.Debug("status recieved: ", grepResponse.ExecutionStatus) 673 | excecutionStatus = grepResponse.ExecutionStatus 674 | 675 | if unmarshalErr != nil { 676 | StopSpinner(spinner, false) 677 | log.Error(err) 678 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 679 | } 680 | 681 | if excecutionStatus != "IN_PROGRESS" { 682 | if excecutionStatus == "FAILURE" { 683 | StopSpinner(spinner, false) 684 | } else { 685 | StopSpinner(spinner, true) 686 | } 687 | printJsonOutput(byt) 688 | return 689 | } 690 | } 691 | 692 | StopSpinner(spinner, false) 693 | Abort(GetGlobalErrorMessage("500"), 500-HttpCodeExitCodeDiff) 694 | } 695 | 696 | func (svc Service) ConnectivityProblems(connectivityProblemsRequest ConnectivityProblemsRequest) { 697 | 698 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 699 | 700 | byt, err := svc.api.ConnectivityProblemsPost(connectivityProblemsRequest) 701 | if err != nil { 702 | StopSpinner(spinner, false) 703 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 704 | } 705 | var connectivityProblemsResponse ConnectivityProblemsResponse 706 | unmarshalErr := json.Unmarshal(*byt, &connectivityProblemsResponse) 707 | 708 | if unmarshalErr != nil { 709 | StopSpinner(spinner, false) 710 | log.Error(err) 711 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 712 | } 713 | 714 | var excecutionStatus string 715 | 716 | for i := 0; i < 6; i++ { 717 | 718 | log.Debug("Waiting for ", connectivityProblemsResponse.RetryAfter, " seconds\n") 719 | time.Sleep(time.Duration(connectivityProblemsResponse.RetryAfter) * time.Second) 720 | 721 | log.Debug("Checking Execution status...") 722 | byt, err := svc.api.ConnectivityProblemsGet(connectivityProblemsResponse.Link) 723 | if err != nil { 724 | StopSpinner(spinner, false) 725 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 726 | } 727 | 728 | unmarshalErr := json.Unmarshal(*byt, &connectivityProblemsResponse) 729 | 730 | log.Debug("status recieved: ", connectivityProblemsResponse.ExecutionStatus) 731 | excecutionStatus = connectivityProblemsResponse.ExecutionStatus 732 | 733 | if unmarshalErr != nil { 734 | StopSpinner(spinner, false) 735 | log.Error(err) 736 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 737 | } 738 | 739 | if excecutionStatus != "IN_PROGRESS" { 740 | if excecutionStatus == "FAILURE" { 741 | StopSpinner(spinner, false) 742 | } else { 743 | StopSpinner(spinner, true) 744 | } 745 | printJsonOutput(byt) 746 | return 747 | } 748 | } 749 | 750 | StopSpinner(spinner, false) 751 | Abort(GetGlobalErrorMessage("500"), 500-HttpCodeExitCodeDiff) 752 | } 753 | 754 | func (svc Service) ContentProblems(contentProblemsRequest ContentProblemsRequest) { 755 | 756 | spinner := StartSpinner(GetMessageForKey(svc.cmd, SpinnerMessage)) 757 | 758 | byt, err := svc.api.ContentProblemsPost(contentProblemsRequest) 759 | if err != nil { 760 | StopSpinner(spinner, false) 761 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 762 | } 763 | var contentProblemsResponse ContentProblemsResponse 764 | unmarshalErr := json.Unmarshal(*byt, &contentProblemsResponse) 765 | 766 | if unmarshalErr != nil { 767 | StopSpinner(spinner, false) 768 | log.Error(unmarshalErr) 769 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 770 | } 771 | 772 | var excecutionStatus string 773 | 774 | for i := 0; i < 6; i++ { 775 | 776 | log.Debug("Waiting for ", contentProblemsResponse.RetryAfter, " seconds\n") 777 | time.Sleep(time.Duration(contentProblemsResponse.RetryAfter) * time.Second) 778 | 779 | log.Debug("Checking Execution status..") 780 | byt, err := svc.api.ContentProblemsGet(contentProblemsResponse.Link) 781 | if err != nil { 782 | StopSpinner(spinner, false) 783 | Abort(err.errorMessage, err.responseCode-HttpCodeExitCodeDiff) 784 | } 785 | 786 | unmarshalErr := json.Unmarshal(*byt, &contentProblemsResponse) 787 | 788 | log.Debug("status recieved: ", contentProblemsResponse.ExecutionStatus) 789 | excecutionStatus = contentProblemsResponse.ExecutionStatus 790 | 791 | if unmarshalErr != nil { 792 | StopSpinner(spinner, false) 793 | log.Error(unmarshalErr) 794 | Abort(GetGlobalErrorMessage(ResponseParsingError), ParsingErrExitCode) 795 | } 796 | 797 | if excecutionStatus != "IN_PROGRESS" { 798 | if excecutionStatus == "FAILURE" { 799 | StopSpinner(spinner, false) 800 | } else { 801 | StopSpinner(spinner, true) 802 | } 803 | printJsonOutput(byt) 804 | return 805 | } 806 | } 807 | StopSpinner(spinner, false) 808 | Abort(GetGlobalErrorMessage("500"), 500-HttpCodeExitCodeDiff) 809 | } 810 | -------------------------------------------------------------------------------- /internal/template.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | var isEdgeIpTemplate = "{{.IpAddress}} is an Edge IP" 4 | var isNotEdgeIpTemplate = "{{.IpAddress}} is not an Edge IP" 5 | 6 | var digTemplate = ` 7 | {{ header "DIG RESULTS" }} 8 | 9 | {{ header "REQUEST"}} 10 | {{ bold "Hostname:"}} {{.Request.Hostname}} 11 | {{ bold "Edge server:"}} {{.InternalIp}} {{locationToString .EdgeIpLocation}} 12 | {{ bold "Query Type:"}} {{.Request.QueryType}} 13 | {{ bold "Request Date:"}} {{dateToString .CreatedTime}} 14 | {{ bold "Request By:"}} {{.CreatedBy}} 15 | 16 | {{ header "RESPONSE"}} 17 | {{ .Result.Result}} ` 18 | 19 | var userDiagnosticsCreateTemplate = ` 20 | {{ bold "Here is your link!"}} 21 | Copy and send the link below to the end users who are experiencing the content delivery problem. 22 | Each link is active for 7 days and has a limit of 50 submissions. 23 | 24 | link: {{ bold .DiagnosticLink}}` 25 | 26 | var userDiagnosticsGetTemplate = ` 27 | {{ header "USER DIAGNOSTIC DATA"}} 28 | {{ bold "Generated on"}}: {{ .CreatedTime}} 29 | {{ bold "Notes" }}: {{ .Note }} 30 | {{ bold "URL" }}: {{ .URL }} 31 | {{ bold "Diagnostic Link" }}: {{ .DiagnosticLink }} 32 | {{ bold "Link Status" }}: {{ capsToTitle .DiagnosticLinkStatus }} 33 | {{ italic "To check the data in JSON format, add the '--json' flag."}} 34 | ` 35 | var translateUrlTemplate = ` 36 | {{ header "Translate url"}} 37 | 38 | {{ bold "Type Code"}}: {{ .TranslatedUrl.TypeCode}} 39 | {{ bold "Cache Key Hostname"}}: {{ .TranslatedUrl.CacheKeyHostname}} 40 | {{ bold "CP Code"}}: {{ .TranslatedUrl.CpCode}} 41 | {{ bold "Serial Number"}}: {{ .TranslatedUrl.SerialNumber}} 42 | {{ bold "TTL"}}: {{ .TranslatedUrl.Ttl}} ` 43 | 44 | var curlTemplate = ` 45 | {{ header "CURL RESULTS"}} 46 | 47 | {{ header "REQUEST"}} 48 | {{ bold "URL"}}: {{ .Request.Url}} 49 | {{ bold "Edge server:"}} {{.InternalIp}} {{locationToString .EdgeIpLocation}} {{if .SiteShieldIp}} 50 | {{ bold "Site shield IP"}}: {{.SiteShieldIp}} {{locationToString .SiteShieldIpLocation}} {{end}} 51 | {{ bold "IP version"}}: {{.Request.IpVersion}} 52 | {{ bold "Request header"}}: {{.Request.RequestHeaders}} 53 | {{ bold "Request date"}}: {{isoDate .CreatedTime}} 54 | {{ bold "Request by"}}: {{.CreatedBy}} 55 | 56 | {{ header "Response Headers"}}{{ range $ind, $value := .CurlOutput.ResponseHeaderList }} 57 | {{ splitHeaderValue $value }}{{ end }}{{if .CurlOutput.Timing.DnsLookupTime}} 58 | 59 | {{ header "Timing Data"}} 60 | {{ bold "DNS Lookup Time"}}: {{ .CurlOutput.Timing.DnsLookupTime}} 61 | {{ bold "TCP Connection Time"}}: {{ .CurlOutput.Timing.TcpConnectionTime}} 62 | {{ bold "SSL Connection Time"}}: {{ .CurlOutput.Timing.SslConnectionTime}} 63 | {{ bold "Time To First Byte"}}: {{ .CurlOutput.Timing.TimeToFirstByte}} 64 | {{ bold "Total Time"}}: {{ .CurlOutput.Timing.TotalTime}}{{ end }} 65 | ` 66 | var mtrDetailsTemplate = ` 67 | {{ header "MTR RESULTS" }} 68 | 69 | {{ header "REQUEST" }} 70 | {{ bold "Source" }}: {{ .SourceInternalIP }} {{ locationToString .SourceIPLocation }} 71 | {{ bold "Destination" }}: {{ .DestinationInternalIP }} {{ locationToString .DestinationIPLocation }} {{if .SiteShieldIp}} 72 | {{ bold "Site shield IP"}}: {{.SiteShieldIp}} {{locationToString .SiteShieldIpLocation}} {{end}} 73 | {{ bold "Request Date:"}} {{dateToString .CreatedTime}} 74 | {{ bold "Request By:"}} {{.CreatedBy}} 75 | 76 | {{ header "Network Connectivity Test from" }} {{ header .SourceInternalIP }} {{ header "to" }} {{ header .DestinationInternalIP }} 77 | ` 78 | -------------------------------------------------------------------------------- /internal/types.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | //Geolocation 4 | type GeoLocation struct { 5 | AreaCode string `json:"areaCode"` 6 | AsNum *int `json:"asNumber"` 7 | City string `json:"city"` 8 | ClientIP string `json:"clientIp"` 9 | Continent string `json:"continent"` 10 | CountryCode string `json:"countryCode"` 11 | County string `json:"county"` 12 | DMA *int `json:"dma"` 13 | FIPS string `json:"fips"` 14 | Latitude *float32 `json:"latitude"` 15 | Longitude *float32 `json:"longitude"` 16 | MSA *int `json:"msa"` 17 | Network string `json:"network"` 18 | NetworkType string `json:"networkType"` 19 | PMSA *int `json:"pmsa"` 20 | Proxy string `json:"proxy"` 21 | RegionCode string `json:"regionCode"` 22 | Throughput string `json:"throughput"` 23 | TimeZone string `json:"timeZone"` 24 | ZipCode string `json:"zipCode"` 25 | } 26 | 27 | // VerifyLocateIp Result 28 | type VerifyLocateIpData struct { 29 | IsEdgeIp bool `json:"isEdgeIp"` 30 | GeoLocation GeoLocation `json:"geoLocation"` 31 | } 32 | 33 | // VerifyLocateIpRequest 34 | type VerifyLocateIpRequest struct { 35 | IpAddress string `json:"ipAddress"` 36 | } 37 | 38 | // VerifyLocateIpResponse 39 | type VerifyLocateIpResponse struct { 40 | Request VerifyLocateIpRequest `json:"request"` 41 | CreatedTime string `json:"createdTime"` 42 | CreatedBy string `json:"createdBy"` 43 | ExecutionStatus string `json:"executionStatus"` 44 | Result VerifyLocateIpData `json:"result"` 45 | } 46 | 47 | // VerifyLocateIpsRequest 48 | type VerifyLocateIpsRequest struct { 49 | IpAddresses []string `json:"ipAddresses"` 50 | } 51 | 52 | // VerifyLocateIpsData 53 | type VerifyLocateIpsData struct { 54 | ExecutionStatus string `json:"executionStatus"` 55 | IpAddress string `json:"ipAddress"` 56 | IsEdgeIp bool `json:"isEdgeIp"` 57 | GeoLocation GeoLocation `json:"geoLocation"` 58 | } 59 | 60 | // VerifyLocateIpsResponse 61 | type VerifyLocateIpsResponse struct { 62 | Request VerifyLocateIpsRequest `json:"request"` 63 | CreatedTime string `json:"createdTime"` 64 | CreatedBy string `json:"createdBy"` 65 | ExecutionStatus string `json:"executionStatus"` 66 | Result []VerifyLocateIpsData `json:"results"` 67 | } 68 | 69 | //DigInfo 70 | type DigInfo struct { 71 | Result string `json:"result"` 72 | } 73 | 74 | //DnsRecord 75 | type DnsRecord struct { 76 | Domain string `json:"domain"` 77 | Hostname string `json:"hostName"` 78 | Ttl string `json:"ttl"` 79 | RecordClass string `json:"recordClass"` 80 | RecordType string `json:"recordType"` 81 | PreferenceValue int `json:"preferenceValue"` 82 | Value string `json:"value"` 83 | } 84 | 85 | //DigRequest 86 | type DigRequest struct { 87 | Hostname string `json:"hostname"` 88 | QueryType string `json:"queryType"` 89 | ClientLocation string `json:"edgeLocationId,omitempty"` 90 | EdgeServerIp string `json:"edgeIp,omitempty"` 91 | IsGtmHostName bool `json:"isGtmHostname"` 92 | } 93 | 94 | //DigResponse 95 | type DigResponse struct { 96 | Request DigRequest `json:"request"` 97 | CreatedTime string `json:"createdTime"` 98 | CreatedBy string `json:"createdBy"` 99 | CompletedTime string `json:"completedTime"` 100 | ExecutionStatus string `json:"executionStatus"` 101 | EdgeIpLocation EdgeIpLocation `json:"edgeIpLocation"` 102 | InternalIp string `json:"internalIp"` 103 | Result DigInfo `json:"result"` 104 | SuggestedActions []string `json:"suggestedActions"` 105 | Type string `json:"type"` 106 | Title string `json:"title"` 107 | Status int `json:"status"` 108 | Detail string `json:"detail"` 109 | } 110 | 111 | // UserDiagnosticsDataRequest 112 | type UserDiagnosticsDataRequest struct { 113 | Url string `json:"url,omitempty"` 114 | Note string `json:"note,omitempty"` 115 | IpaHostname string `json:"ipaHostname,omitempty"` 116 | } 117 | 118 | // UserDiagnosticsDataGroupDetails 119 | type UserDiagnosticsDataGroupDetails struct { 120 | GroupID string `json:"groupId"` 121 | Note string `json:"note"` 122 | URL string `json:"url"` 123 | IpaHostname string `json:"ipaHostname"` 124 | CreatedTime string `json:"createdTime"` 125 | DiagnosticLink string `json:"diagnosticLink"` 126 | DiagnosticLinkStatus string `json:"diagnosticLinkStatus"` 127 | RecordCount int `json:"recordCount"` 128 | CreatedBy string `json:"createdBy"` 129 | } 130 | 131 | //add all list responses, use this as a container 132 | type ListResponse struct { 133 | Groups []UserDiagnosticsDataGroupDetails `json:"groups,omitempty"` 134 | } 135 | 136 | type ArlOutput struct { 137 | TypeCode string `json:"typeCode"` 138 | CacheKeyHostname string `json:"cacheKeyHostname"` 139 | CpCode string `json:"cpCode"` 140 | SerialNumber string `json:"serialNumber"` 141 | Ttl string `json:"ttl"` 142 | Pragma string `json:"pragma"` 143 | CacheControl string `json:"cacheControl"` 144 | } 145 | 146 | type ArlRequest struct { 147 | Url string `json:"url"` 148 | } 149 | 150 | type ArlContainer struct { 151 | Request ArlRequest `json:"request"` 152 | TranslatedUrl ArlOutput `json:"translatedUrl"` 153 | } 154 | 155 | // ErrorTranslatorRequest 156 | type ErrorTranslatorRequest struct { 157 | ErrorCode string `json:"errorCode"` 158 | TraceForwardLogs bool `json:"traceForwardLogs,omitempty"` 159 | } 160 | 161 | // ErrorTranslatorResponse 162 | type ErrorTranslatorResponse struct { 163 | ExecutionStatus string `json:"executionStatus"` 164 | RetryAfter int `json:"retryAfter"` 165 | Link string `json:"link"` 166 | } 167 | 168 | type UrlHealthCheckRequest struct { 169 | Url string `json:"url"` 170 | EdgeLocationId string `json:"edgeLocationId,omitempty"` 171 | EdgeIp string `json:"spoofEdgeIp,omitempty"` 172 | Port int `json:"port,omitempty"` 173 | PacketType string `json:"packetType,omitempty"` 174 | IpVersion string `json:"ipVersion,omitempty"` 175 | RequestHeaders []string `json:"requestHeaders,omitempty"` 176 | QueryType string `json:"queryType,omitempty"` 177 | ViewsAllowed []string `json:"viewsAllowed,omitempty"` 178 | RunFromSiteShield bool `json:"runFromSiteShield,omitempty"` 179 | } 180 | 181 | type UrlHealthCheckResponse struct { 182 | ExecutionStatus string `json:"executionStatus"` 183 | RetryAfter int `json:"retryAfter"` 184 | Link string `json:"link"` 185 | } 186 | 187 | type EdgeLocation struct { 188 | Id string `json:"id"` 189 | Value string `json:"value"` 190 | } 191 | 192 | type EdgeLocationContainer struct { 193 | EdgeLocations []EdgeLocation `json:"edgeLocations"` 194 | } 195 | 196 | type IpaHostnameResponse struct { 197 | Hostnames []string `json:"hostnames"` 198 | } 199 | 200 | type GtmProperty struct { 201 | Property string `json:"property"` 202 | Domain string `json:"domain"` 203 | Hostname string `json:"hostname"` 204 | DomainId int `json:"domainId"` 205 | } 206 | 207 | type GtmPropertyContainer struct { 208 | GtmProperties []GtmProperty `json:"gtmProperties"` 209 | } 210 | 211 | type GTMPropertyIps struct { 212 | Property string `json:"property"` 213 | Domain string `json:"domain"` 214 | TestIps []string `json:"testIps"` 215 | Targets []string `json:"targets"` 216 | } 217 | 218 | type GtmPropertyIpsContainer struct { 219 | GTMPropertyIps GTMPropertyIps `json:"gtmPropertyIps"` 220 | } 221 | 222 | // CurlRequest 223 | type CurlRequest struct { 224 | Url string `json:"url"` 225 | EdgeIp string `json:"edgeIp,omitempty"` 226 | EdgeLocationId string `json:"edgeLocationId,omitempty"` 227 | IpVersion string `json:"ipVersion,omitempty"` 228 | RequestHeaders []string `json:"requestHeaders,omitempty"` 229 | RunFromSiteShield bool `json:"runFromSiteShield,omitempty"` 230 | } 231 | 232 | //EstatsWrapper 233 | type EstatsResultWrapper struct { 234 | EstatsResult EstatsResult `json:"estatsResult"` 235 | EstatsLogLines []EstatsGrepResponse `json:"estatsLogLines,omitempty"` 236 | } 237 | 238 | //EstatsRequest 239 | type EstatsRequest struct { 240 | Url string `json:"url,omitempty"` 241 | CpCode int `json:"cpCode,omitempty"` 242 | Delivery string `json:"delivery,omitempty"` 243 | ErrorType string `json:"errorType,omitempty"` 244 | } 245 | 246 | type EstatsData struct { 247 | EdgeErrors int `json:"edgeErrors"` 248 | EdgeHits int `json:"edgeHits"` 249 | EdgeFailurePercentage float64 `json:"edgeFailurePercentage"` 250 | OriginErrors int `json:"originErrors"` 251 | OriginHits int `json:"originHits"` 252 | OriginFailurePercentage float64 `json:"originFailurePercentage"` 253 | TopEdgeIpsWithError []EdgeIpInfo `json:"topEdgeIpsWithError"` 254 | TopEdgeIpsWithErrorFromOrigin []EdgeIpInfo `json:"topEdgeIpsWithErrorFromOrigin"` 255 | TopEdgeIpsWithSuccess []EdgeIpInfo `json:"topEdgeIpsWithSuccess"` 256 | TopEdgeIpsWithSuccessFromOrigin []EdgeIpInfo `json:"topEdgeIpsWithSuccessFromOrigin"` 257 | EdgeStatusCodeDistribution []StatusCodeDistribution `json:"edgeStatusCodeDistribution"` 258 | OriginStatusCodeDistribution []StatusCodeDistribution `json:"originStatusCodeDistribution"` 259 | } 260 | 261 | type EstatsResult struct { 262 | Request EstatsRequest `json:"request"` 263 | RequestId int `json:"requestId"` 264 | CreatedBy string `json:"createdBy"` 265 | CreatedTime string `json:"createdTime"` 266 | CompletedTime string `json:"completedTime"` 267 | ExecutionStatus string `json:"executionStatus"` 268 | Result EstatsData `json:"result"` 269 | } 270 | 271 | type EdgeIpInfo struct { 272 | ErrorId string `json:"errorId,omitempty"` 273 | LogLinesCount int `json:"logLinesCount"` 274 | EdgeIp string `json:"edgeIp"` 275 | EdgeIpLocation EdgeIpLocation `json:"edgeIpLocation"` 276 | HttpStatus int `json:"httpStatus"` 277 | Hits int `json:"hits"` 278 | ObjectStatus []ObjectStatus `json:"objectStatus"` 279 | ErrorCode string `json:"errorCode"` 280 | EdgeLogsLink string `json:"edgeLogsLink"` 281 | } 282 | 283 | type StatusCodeDistribution struct { 284 | HttpStatus int `json:"httpStatus"` 285 | Hits int `json:"hits"` 286 | Percentage float64 `json:"percentage"` 287 | } 288 | 289 | type ObjectStatus struct { 290 | Code string `json:"code"` 291 | Description string `json:"description"` 292 | } 293 | 294 | type EdgeIpLocation struct { 295 | City string `json:"city"` 296 | RegionCode string `json:"regionCode,omitempty"` 297 | CountryCode string `json:"countryCode"` 298 | AsNum *int `json:"asNumber,omitempty"` 299 | Latitude *float32 `json:"latitude,omitempty"` 300 | Longitude *float32 `json:"longitude,omitempty"` 301 | } 302 | 303 | type Warning struct { 304 | Key string `json:"key"` 305 | Message string `json:"message"` 306 | } 307 | 308 | type Timing struct { 309 | DnsLookupTime float64 `json:"dnsLookupTime,omitempty"` 310 | TcpConnectionTime float64 `json:"tcpConnectionTime,omitempty"` 311 | SslConnectionTime float64 `json:"sslConnectionTime,omitempty"` 312 | TimeToFirstByte float64 `json:"timeToFirstByte,omitempty"` 313 | TotalTime float64 `json:"totalTime,omitempty"` 314 | } 315 | 316 | type CurlOutput struct { 317 | HttpStatusCode int `json:"httpStatusCode"` 318 | ResponseHeaders map[string]string `json:"responseHeaders"` 319 | Timing Timing `json:"timing,omitempty"` 320 | ResponseBody string `json:"responseBody"` 321 | ResponseHeaderList []string `json:"responseHeaderList"` 322 | HttpVersion string `json:"httpVersion"` 323 | ReasonPhrase string `json:"reasonPhrase"` 324 | PartialSuccess bool `json:"partialSuccess"` 325 | } 326 | 327 | type CurlResponse struct { 328 | Request CurlRequest `json:"request"` 329 | CreatedBy string `json:"createdBy"` 330 | CreatedTime string `json:"createdTime"` 331 | CompletedTime string `json:"completedTime"` 332 | ExecutionStatus string `json:"executionStatus"` 333 | InternalIp string `json:"internalIp"` 334 | EdgeIpLocation EdgeIpLocation `json:"edgeIpLocation"` 335 | SiteShieldIp string `json:"siteShieldIp"` 336 | SiteShieldIpLocation EdgeIpLocation `json:"siteShieldIpLocation"` 337 | CurlOutput CurlOutput `json:"result"` 338 | SuggestedActions []string `json:"suggestedActions"` 339 | Warning Warning `json:"warning"` 340 | } 341 | 342 | // MtrRequest 343 | type MtrRequest struct { 344 | Source string `json:"source"` 345 | Destination string `json:"destination"` 346 | PacketType string `json:"packetType"` 347 | SourceType string `json:"sourceType,omitempty"` 348 | DestinationType string `json:"destinationType,omitempty"` 349 | IPVersion string `json:"ipVersion,omitempty"` 350 | Port int `json:"port,omitempty"` 351 | GtmHostname string `json:"gtmHostname,omitempty"` 352 | SiteShieldHostname string `json:"siteShieldHostname,omitempty"` 353 | } 354 | 355 | // Mtr hop 356 | type MtrHop struct { 357 | Number int `json:"number"` 358 | IP string `json:"ip"` 359 | IPLocation EdgeIpLocation `json:"ipLocation"` 360 | Host string `json:"host"` 361 | PacketLoss float64 `json:"packetLoss"` 362 | SentPackets int `json:"sentPackets"` 363 | LastPacketLatency float64 `json:"lastPacketLatency"` 364 | AverageLatency float64 `json:"averageLatency"` 365 | BestRtt float64 `json:"bestRtt"` 366 | WorstRtt float64 `json:"worstRtt"` 367 | StandardDeviation float64 `json:"standardDeviation"` 368 | } 369 | 370 | // MtrResult 371 | type MtrResult struct { 372 | Host string `json"host"` 373 | Hops []MtrHop `json:"hops"` 374 | } 375 | 376 | // MtrResponse 377 | type MtrResponse struct { 378 | SourceIPLocation EdgeIpLocation `json:"sourceIpLocation"` 379 | DestinationIPLocation EdgeIpLocation `json:"destinationIpLocation"` 380 | SourceContext string `json:"sourceContext"` 381 | DestinationContext string `json:"destinationContext"` 382 | SourceInternalIP string `json:"sourceInternalIp"` 383 | DestinationInternalIP string `json:"destinationInternalIp"` 384 | CreatedBy string `json:"createdBy"` 385 | CreatedTime string `json:"createdTime"` 386 | SiteShieldIp string `json:"siteShieldIp"` 387 | SiteShieldIpLocation EdgeIpLocation `json:"siteShieldIpLocation"` 388 | Result MtrResult `json:"result"` 389 | SuggestedActions []string `json:"suggestedActions"` 390 | } 391 | 392 | type HttpStatusCode struct { 393 | Comparison string `json:"comparison"` 394 | Value []string `json:"value"` 395 | } 396 | 397 | type Arl struct { 398 | Comparison string `json:"comparison"` 399 | Value []string `json:"value"` 400 | } 401 | 402 | type GrepRequest struct { 403 | CpCodes []int `json:"cpCodes,omitempty"` 404 | Hostnames []string `json:"hostnames,omitempty"` 405 | EdgeIp string `json:"edgeIp"` 406 | LogType string `json:"logType"` 407 | Start string `json:"start"` 408 | End string `json:"end"` 409 | UserAgents []string `json:"userAgent,omitempty"` 410 | HttpStatusCodes *HttpStatusCode `json:"httpStatusCodes,omitempty"` 411 | Arls *Arl `json:"arls,omitempty"` 412 | ClientIps []string `json:"clientIps,omitempty"` 413 | } 414 | 415 | type GrepResponse struct { 416 | ExecutionStatus string `json:"executionStatus"` 417 | RetryAfter int `json:"retryAfter"` 418 | Link string `json:"link"` 419 | } 420 | 421 | type EstatsGrepResponse struct { 422 | LogLinesCount int `json:"logLinesCount"` 423 | ExecutionStatus string `json:"executionStatus"` 424 | CreatedTime string `json:"createdTime"` 425 | CompletedTime string `json:"completedTime"` 426 | CreatedBy string `json:"createdBy"` 427 | LogsContainer LogsContainer `json:"result"` 428 | SuggestedActions []string `json:"suggestedActions"` 429 | EdgeIpLocation EdgeIpLocation `json:"edgeIpLocation"` 430 | Warning Warning `json:"warning"` 431 | } 432 | 433 | type LogsContainer struct { 434 | Logs []map[string]string `json:"logs"` 435 | Legend Legend `json:"legend"` 436 | } 437 | 438 | type Legend struct { 439 | LogType map[string]string `json:"logType"` 440 | ObjectStatus map[string]string `json:"objectStatus"` 441 | ObjectStatus2 map[string]string `json:"objectStatus2"` 442 | } 443 | 444 | type ConnectivityProblemsRequest struct { 445 | Url string `json:"url"` 446 | EdgeLocationId string `json:"edgeLocationId,omitempty"` 447 | SpoofEdgeIp string `json:"spoofEdgeIp,omitempty"` 448 | ClientIp string `json:"clientIp,omitempty"` 449 | RequestHeaders []string `json:"requestHeaders,omitempty"` 450 | IpVersion string `json:"ipVersion,omitempty"` 451 | PacketType string `json:"packetType,omitempty"` 452 | Port int `json:"port,omitempty"` 453 | RunFromSiteShield bool `json:"runFromSiteShield,omitempty"` 454 | } 455 | 456 | type ConnectivityProblemsResponse struct { 457 | ExecutionStatus string `json:"executionStatus"` 458 | RetryAfter int `json:"retryAfter"` 459 | Link string `json:"link"` 460 | } 461 | 462 | type ContentProblemsRequest struct { 463 | Url string `json:"url"` 464 | EdgeLocationId string `json:"edgeLocationId,omitempty"` 465 | EdgeIp string `json:"spoofEdgeIp,omitempty"` 466 | RequestHeaders []string `json:"requestHeaders,omitempty"` 467 | IpVersion string `json:"ipVersion,omitempty"` 468 | RunFromSiteShield bool `json:"runFromSiteShield,omitempty"` 469 | } 470 | 471 | type ContentProblemsResponse struct { 472 | ExecutionStatus string `json:"executionStatus"` 473 | RetryAfter int `json:"retryAfter"` 474 | Link string `json:"link"` 475 | } 476 | -------------------------------------------------------------------------------- /internal/utils.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/briandowns/spinner" 15 | "github.com/fatih/camelcase" 16 | "github.com/fatih/color" 17 | log "github.com/sirupsen/logrus" 18 | ) 19 | 20 | func CamelToTitle(inp string) string { 21 | inp = strings.Title(inp) 22 | arr := camelcase.Split(inp) 23 | out := strings.Join(arr, " ") 24 | out = strings.Replace(out, "Url", "URL", -1) 25 | return out 26 | } 27 | 28 | func CapsToTitle(inp string) string { 29 | return strings.Title(strings.ToLower(inp)) 30 | } 31 | 32 | func StartSpinner(message string) *spinner.Spinner { 33 | // making different variable name from import package name. 34 | s := spinner.New([]string{". ", ".. ", "... ", ".... ", "..... ", "......"}, 300*time.Millisecond) 35 | s.Writer = os.Stderr // Out-of-band info like progress info should go to stderr 36 | s.Prefix = message 37 | s.Start() 38 | return s 39 | } 40 | 41 | func StopSpinner(s *spinner.Spinner, success bool) { 42 | if success { 43 | s.FinalMSG = s.Prefix + "...... " + color.GreenString("[OK]") 44 | } else { 45 | s.FinalMSG = s.Prefix + "...... " + color.RedString("[FAIL]") 46 | } 47 | s.Stop() 48 | fmt.Fprintln(s.Writer) // Add a new line after the s 49 | } 50 | 51 | // Check contains and irrespective of case. 52 | func ContainsIgnoreCase(a string, b string) bool { 53 | return strings.Contains(strings.ToLower(a), strings.ToLower(b)) 54 | } 55 | 56 | // Check contains and irrespective of case. 57 | func ContainsInArray(array []string, inputString string) bool { 58 | var result = false 59 | for _, x := range array { 60 | if x == strings.ToLower(inputString) { 61 | result = true 62 | break 63 | } 64 | } 65 | 66 | return result 67 | } 68 | 69 | func ConvertBooleanToYesOrNo(input bool) string { 70 | if input { 71 | return "Yes" 72 | } 73 | return "No" 74 | } 75 | 76 | func FormatTime(inputTime string) string { 77 | layout := "2006-01-02T15:04:05+0000" 78 | myDate, err := time.Parse(layout, inputTime) 79 | if err != nil { 80 | fmt.Println(err) 81 | } 82 | // convert this date to desired format when decided. 83 | return myDate.Format("01/02/2006, 15:04 PM -07:00") 84 | } 85 | 86 | //Get all placeholders in string inside {{}} 87 | func GetPlaceHoldersInString(errorMessage, regex string) []string { 88 | 89 | r := regexp.MustCompile(regex) 90 | matches := r.FindAllStringSubmatch(errorMessage, -1) 91 | var placeHolders = make([]string, len(matches)) 92 | for i, v := range matches { 93 | placeHolders[i] = v[1] 94 | } 95 | 96 | return placeHolders 97 | } 98 | 99 | // 2021-10-14T16:14:12+0000 to Thursday, October 14, 2021 100 | func isoToDate(isoDate string) string { 101 | isoDate = strings.Replace(isoDate, "+0000", "Z", 1) 102 | date, _ := time.Parse(time.RFC3339, isoDate) 103 | return date.Format("Monday, January 2, 2006") 104 | } 105 | 106 | // EdgeIpLocation to (city, regionCode, countryCode)(ASN asNumber) 107 | func (loc EdgeIpLocation) toString() string { 108 | str := "" 109 | sep := ", " 110 | var arr []string 111 | 112 | if loc.City != "" { 113 | arr = append(arr, CapsToTitle(loc.City)) 114 | } 115 | if loc.RegionCode != "" { 116 | arr = append(arr, loc.RegionCode) 117 | } 118 | if loc.CountryCode != "" { 119 | arr = append(arr, loc.CountryCode) 120 | } 121 | 122 | if len(arr) > 0 { 123 | str = "(" + strings.Join(arr, sep) + ")" 124 | } 125 | 126 | if loc.AsNum != nil { 127 | str += " (ASN " + strconv.Itoa(*loc.AsNum) + ")" 128 | } 129 | 130 | return str 131 | } 132 | 133 | func (loc EdgeIpLocation) toStringNoBrackets() string { 134 | str := "" 135 | sep := ", " 136 | var arr []string 137 | 138 | if loc.City != "" { 139 | arr = append(arr, CapsToTitle(loc.City)) 140 | } 141 | if loc.RegionCode != "" { 142 | arr = append(arr, loc.RegionCode) 143 | } 144 | if loc.CountryCode != "" { 145 | arr = append(arr, loc.CountryCode) 146 | } 147 | 148 | if len(arr) > 0 { 149 | str = strings.Join(arr, sep) 150 | } 151 | 152 | if loc.AsNum != nil { 153 | str += " (ASN " + strconv.Itoa(*loc.AsNum) + ")" 154 | } 155 | 156 | return str 157 | } 158 | 159 | func ReadStdin() []byte { 160 | file := os.Stdin 161 | fi, err := file.Stat() 162 | if err != nil { 163 | Abort("Error in stdin", CliErrExitCode) 164 | } 165 | size := fi.Size() 166 | if size > 0 { 167 | log.Debug("%v bytes available in Stdin\n", size) 168 | var reader = bufio.NewReader(os.Stdin) 169 | jsonData, err := ioutil.ReadAll(reader) 170 | if err != nil { 171 | Abort("Error in reading stdin", ParsingErrExitCode) 172 | } 173 | return jsonData 174 | } 175 | log.Debug("Stdin is empty") 176 | return nil 177 | } 178 | 179 | func ByteArrayToStruct(byt []byte, objPtr interface{}) { 180 | if err := json.Unmarshal(byt, objPtr); err != nil { 181 | log.Debug(err) 182 | Abort("Unable to parse the json", ParsingErrExitCode) 183 | } 184 | } 185 | 186 | func SplitCurlHeaderString(responseHeaderString string) string { 187 | splitArr := strings.SplitAfterN(responseHeaderString, curlHeaderSeparator, 2) 188 | value := "" 189 | if len(splitArr) == 2 { 190 | value = splitArr[1] 191 | } 192 | return bold(splitArr[0]) + value 193 | } 194 | -------------------------------------------------------------------------------- /internal/validator.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/url" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | type Validator struct { 14 | cmd *cobra.Command 15 | jsonData []byte 16 | } 17 | 18 | func NewValidator(cmd *cobra.Command, jsonData []byte) *Validator { 19 | return &Validator{cmd, jsonData} 20 | } 21 | 22 | func isValidIp(str string) bool { 23 | ip := net.ParseIP(strings.TrimSpace(str)) 24 | return ip != nil 25 | } 26 | 27 | func (validator Validator) validateIpArg(ipAddress string, field string) { 28 | 29 | if !isValidIp(ipAddress) { 30 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForArg(validator.cmd, Invalid, field), CmdErrExitCode) 31 | } 32 | } 33 | 34 | func (validator Validator) validateIpFlag(ipAddress, field string, required bool) { 35 | 36 | if ipAddress == "" { 37 | if required { 38 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, field), CmdErrExitCode) 39 | } 40 | return 41 | } 42 | if !isValidIp(ipAddress) { 43 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, field), CmdErrExitCode) 44 | } 45 | } 46 | 47 | func isAbsoluteURL(str string) bool { 48 | urlCheck, err := url.Parse(str) 49 | if err != nil || !urlCheck.IsAbs() { 50 | return false 51 | } 52 | return true 53 | } 54 | 55 | func (validator Validator) validateUrlArg(url string, field string) { 56 | 57 | if !isAbsoluteURL(url) { 58 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForArg(validator.cmd, Invalid, field), CmdErrExitCode) 59 | } 60 | } 61 | 62 | func (validator Validator) validateUrlFlag(url, field string, required bool) { 63 | 64 | if url == "" { 65 | if required { 66 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, field), CmdErrExitCode) 67 | } 68 | return 69 | } 70 | if !isAbsoluteURL(url) { 71 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, field), CmdErrExitCode) 72 | } 73 | } 74 | 75 | func (validator Validator) ValidatePortFlag(port string, portInt *int, field string, required bool) { 76 | 77 | if port == "" { 78 | if required { 79 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, field), CmdErrExitCode) 80 | } 81 | return 82 | } 83 | 84 | if port != "80" && port != "443" { 85 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, field), CmdErrExitCode) 86 | } 87 | 88 | *portInt, _ = strconv.Atoi(port) 89 | 90 | } 91 | 92 | func (validator Validator) ValidatePacketTypeFlag(packetType *string, field string, required bool) { 93 | 94 | if *packetType == "" { 95 | if required { 96 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, field), CmdErrExitCode) 97 | } 98 | return 99 | } 100 | 101 | *packetType = strings.ToUpper(*packetType) 102 | 103 | if *packetType != TCP && *packetType != ICMP { 104 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, field), CmdErrExitCode) 105 | } 106 | 107 | } 108 | 109 | func (validator Validator) ValidateIpVersionFlag(ipVersion *string, field string, required bool) { 110 | 111 | if *ipVersion == "" { 112 | if required { 113 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, field), CmdErrExitCode) 114 | } 115 | return 116 | } 117 | 118 | *ipVersion = strings.ToUpper(*ipVersion) 119 | 120 | if *ipVersion != IPV4 && *ipVersion != IPV6 { 121 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, field), CmdErrExitCode) 122 | } 123 | 124 | } 125 | 126 | func (validator Validator) ValidateVerifyLocateIpFields(args []string, verifyLocateIpRequest *VerifyLocateIpRequest) { 127 | 128 | if validator.jsonData != nil { 129 | if len(args) > 0 { 130 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 131 | } 132 | *verifyLocateIpRequest = VerifyLocateIpRequest{} 133 | ByteArrayToStruct(validator.jsonData, &verifyLocateIpRequest) 134 | return 135 | } 136 | 137 | if len(args) != 1 { 138 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 1, len(args)), CmdErrExitCode) 139 | } 140 | 141 | validator.validateIpArg(args[0], "ipAddress") 142 | verifyLocateIpRequest.IpAddress = args[0] 143 | } 144 | 145 | func (validator Validator) ValidateVerifyIpOrLocateIpFields(args []string, verifyLocateIpsRequest *VerifyLocateIpsRequest) { 146 | 147 | if validator.jsonData != nil { 148 | if len(args) > 0 { 149 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 150 | } 151 | *verifyLocateIpsRequest = VerifyLocateIpsRequest{} 152 | ByteArrayToStruct(validator.jsonData, &verifyLocateIpsRequest) 153 | return 154 | } 155 | 156 | if len(args) < 1 { 157 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 1, len(args)), CmdErrExitCode) 158 | } 159 | 160 | for _, arg := range args { 161 | validator.validateIpArg(arg, "ipAddress") 162 | verifyLocateIpsRequest.IpAddresses = append(verifyLocateIpsRequest.IpAddresses, arg) 163 | } 164 | } 165 | 166 | func (validator Validator) ValidateUserDiagnosticsCreateFields(args []string, userDiagnosticsDataRequest *UserDiagnosticsDataRequest) { 167 | 168 | if validator.jsonData != nil { 169 | if userDiagnosticsDataRequest.Url != "" || userDiagnosticsDataRequest.IpaHostname != "" || userDiagnosticsDataRequest.Note != "" { 170 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 171 | } 172 | *userDiagnosticsDataRequest = UserDiagnosticsDataRequest{} 173 | ByteArrayToStruct(validator.jsonData, userDiagnosticsDataRequest) 174 | return 175 | } 176 | 177 | if len(args) != 0 { 178 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 0, len(args)), CmdErrExitCode) 179 | } 180 | 181 | if userDiagnosticsDataRequest.Url == "" && userDiagnosticsDataRequest.IpaHostname == "" { 182 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, "any"), CmdErrExitCode) 183 | } else if userDiagnosticsDataRequest.Url != "" && userDiagnosticsDataRequest.IpaHostname != "" { 184 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "exclusive"), CmdErrExitCode) 185 | } 186 | 187 | validator.validateUrlFlag(userDiagnosticsDataRequest.Url, "url", false) 188 | 189 | } 190 | 191 | func (validator Validator) ValidateCurlFields(args []string, curlRequest *CurlRequest) { 192 | 193 | if validator.jsonData != nil { 194 | if len(args) > 0 || curlRequest.EdgeLocationId != "" || curlRequest.EdgeIp != "" || curlRequest.IpVersion != IPV4 || len(curlRequest.RequestHeaders) > 0 { 195 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 196 | } 197 | *curlRequest = CurlRequest{} 198 | ByteArrayToStruct(validator.jsonData, curlRequest) 199 | return 200 | } 201 | 202 | if len(args) != 1 { 203 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 1, len(args)), CmdErrExitCode) 204 | } 205 | 206 | validator.validateUrlArg(args[0], "url") 207 | curlRequest.Url = args[0] 208 | 209 | if curlRequest.EdgeIp != "" && curlRequest.EdgeLocationId != "" { 210 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "exclusive"), CmdErrExitCode) 211 | } 212 | 213 | validator.validateIpFlag(curlRequest.EdgeIp, "edgeServerIp", false) 214 | validator.ValidateIpVersionFlag(&curlRequest.IpVersion, "ipVersion", false) 215 | 216 | } 217 | 218 | func (validator Validator) ValidateDigFields(args []string, digRequest *DigRequest) { 219 | 220 | if validator.jsonData != nil { 221 | if digRequest.Hostname != "" || digRequest.ClientLocation != "" || digRequest.EdgeServerIp != "" || digRequest.QueryType != "A" || digRequest.IsGtmHostName { 222 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 223 | } 224 | *digRequest = DigRequest{} 225 | ByteArrayToStruct(validator.jsonData, digRequest) 226 | return 227 | } 228 | 229 | if len(args) != 0 { 230 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 0, len(args)), CmdErrExitCode) 231 | } 232 | 233 | if digRequest.Hostname == "" { 234 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, "hostname"), CmdErrExitCode) 235 | } 236 | if digRequest.ClientLocation != "" && digRequest.EdgeServerIp != "" { 237 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "exclusive"), CmdErrExitCode) 238 | } 239 | 240 | validator.validateIpFlag(digRequest.EdgeServerIp, "edgeServerIp", false) 241 | 242 | } 243 | 244 | func (validator Validator) ValidateEstatsFields(args []string, estatsRequest *EstatsRequest, logs, enhancedTls, standardTls, edgeErrors, originErrors bool) { 245 | 246 | if validator.jsonData != nil { 247 | if estatsRequest.Url != "" || estatsRequest.CpCode != 0 || enhancedTls || standardTls || edgeErrors || originErrors { 248 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 249 | } 250 | *estatsRequest = EstatsRequest{} 251 | ByteArrayToStruct(validator.jsonData, estatsRequest) 252 | return 253 | } 254 | 255 | if len(args) != 0 { 256 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 0, len(args)), CmdErrExitCode) 257 | } 258 | 259 | if estatsRequest.Url == "" && estatsRequest.CpCode == 0 { 260 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, "any"), CmdErrExitCode) 261 | } else if estatsRequest.Url != "" && estatsRequest.CpCode != 0 { 262 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "exclusive"), CmdErrExitCode) 263 | } 264 | 265 | validator.validateUrlFlag(estatsRequest.Url, "url", false) 266 | 267 | if enhancedTls && standardTls { 268 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "exclusiveDelivery"), CmdErrExitCode) 269 | } 270 | 271 | if enhancedTls { 272 | estatsRequest.Delivery = "ENHANCED_TLS" 273 | } else if standardTls { 274 | estatsRequest.Delivery = "STANDARD_TLS" 275 | } 276 | 277 | if edgeErrors && !originErrors { 278 | estatsRequest.ErrorType = "EDGE_ERRORS" 279 | } else if !edgeErrors && originErrors { 280 | estatsRequest.ErrorType = "ORIGIN_ERRORS" 281 | } 282 | 283 | } 284 | func (validator Validator) ValidateMtrFields(args []string, portStr string, mtrRequest *MtrRequest) { 285 | 286 | if validator.jsonData != nil { 287 | if mtrRequest.Source != "" || mtrRequest.Destination != "" || mtrRequest.GtmHostname != "" || mtrRequest.PacketType != TCP || portStr != "" || mtrRequest.IPVersion != "" { 288 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 289 | } 290 | *mtrRequest = MtrRequest{} 291 | ByteArrayToStruct(validator.jsonData, mtrRequest) 292 | return 293 | } 294 | 295 | if len(args) != 0 { 296 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 0, len(args)), CmdErrExitCode) 297 | } 298 | 299 | if mtrRequest.Source == "" && mtrRequest.SiteShieldHostname == "" { 300 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, "source"), CmdErrExitCode) 301 | } 302 | 303 | if mtrRequest.Destination == "" { 304 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, "destination"), CmdErrExitCode) 305 | } 306 | 307 | //GTM scenario 308 | if mtrRequest.GtmHostname != "" { 309 | if !isValidIp(mtrRequest.Source) { 310 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "gtmScenario"), CmdErrExitCode) 311 | } 312 | if portStr != "" { 313 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Redundant, "port"), CmdErrExitCode) 314 | } 315 | if mtrRequest.IPVersion != "" { 316 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Redundant, "ipVersion"), CmdErrExitCode) 317 | } 318 | return 319 | } 320 | 321 | if mtrRequest.Source != "" { 322 | if isValidIp(mtrRequest.Source) { 323 | mtrRequest.SourceType = sourceTypeEdgeIp 324 | } else { 325 | mtrRequest.SourceType = sourceTypeLocation 326 | } 327 | } 328 | 329 | // destination is IP, --port and --ip-version are redundant 330 | if isValidIp(mtrRequest.Destination) { 331 | if portStr != "" { 332 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Redundant, "port"), CmdErrExitCode) 333 | } 334 | if mtrRequest.IPVersion != "" { 335 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Redundant, "ipVersion"), CmdErrExitCode) 336 | } 337 | mtrRequest.DestinationType = destinationTypeIp 338 | } else { 339 | if mtrRequest.SiteShieldHostname != "" { 340 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "destination"), CmdErrExitCode) 341 | } 342 | //set default values for port and ipversion if destination is hostname 343 | if portStr == "" { 344 | mtrRequest.Port = DefaultPort 345 | } else { 346 | validator.ValidatePortFlag(portStr, &mtrRequest.Port, "port", false) 347 | } 348 | if mtrRequest.IPVersion == "" { 349 | mtrRequest.IPVersion = IPV4 350 | } else { 351 | validator.ValidateIpVersionFlag(&mtrRequest.IPVersion, "ipVersion", false) 352 | } 353 | mtrRequest.DestinationType = destinationTypeHost 354 | } 355 | validator.ValidatePacketTypeFlag(&mtrRequest.PacketType, "packetType", true) 356 | } 357 | 358 | func (validator Validator) ValidateTranslateUrlFields(args []string, translateUrlRequest *ArlRequest) { 359 | 360 | if validator.jsonData != nil { 361 | if len(args) > 0 { 362 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 363 | } 364 | ByteArrayToStruct(validator.jsonData, translateUrlRequest) 365 | return 366 | } 367 | 368 | if len(args) != 1 { 369 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 1, len(args)), CmdErrExitCode) 370 | } 371 | 372 | validator.validateUrlArg(args[0], "url") 373 | translateUrlRequest.Url = args[0] 374 | 375 | } 376 | 377 | func (validator Validator) ValidateUrlHealthCheckFields(args []string, portStr string, urlHealthCheckRequest *UrlHealthCheckRequest, logs, networkConnectivity bool) { 378 | 379 | if validator.jsonData != nil { 380 | if len(args) > 0 || urlHealthCheckRequest.EdgeIp != "" || urlHealthCheckRequest.PacketType != "" || urlHealthCheckRequest.IpVersion != "" || urlHealthCheckRequest.QueryType != "" || 381 | len(urlHealthCheckRequest.RequestHeaders) > 0 || portStr != "" || logs || networkConnectivity { 382 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 383 | } 384 | *urlHealthCheckRequest = UrlHealthCheckRequest{} 385 | ByteArrayToStruct(validator.jsonData, urlHealthCheckRequest) 386 | return 387 | } 388 | 389 | if len(args) != 1 { 390 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 1, len(args)), CmdErrExitCode) 391 | } 392 | 393 | validator.validateUrlArg(args[0], "url") 394 | validator.validateIpFlag(urlHealthCheckRequest.EdgeIp, "edgeServerIp", false) 395 | validator.ValidatePortFlag(portStr, &urlHealthCheckRequest.Port, "port", false) 396 | validator.ValidateIpVersionFlag(&urlHealthCheckRequest.IpVersion, "ipVersion", false) 397 | validator.ValidatePacketTypeFlag(&urlHealthCheckRequest.PacketType, "packetType", false) 398 | 399 | urlHealthCheckRequest.Url = args[0] 400 | 401 | if logs { 402 | urlHealthCheckRequest.ViewsAllowed = append(urlHealthCheckRequest.ViewsAllowed, "LOGS") 403 | } 404 | if networkConnectivity { 405 | urlHealthCheckRequest.ViewsAllowed = append(urlHealthCheckRequest.ViewsAllowed, "CONNECTIVITY") 406 | } 407 | 408 | } 409 | 410 | func (validator Validator) ValidateConnectivityProblemsFields(args []string, portStr string, connectivityProblemsRequest *ConnectivityProblemsRequest) { 411 | 412 | if validator.jsonData != nil { 413 | if len(args) > 0 || len(connectivityProblemsRequest.RequestHeaders) > 0 || connectivityProblemsRequest.SpoofEdgeIp != "" || portStr != "" || 414 | connectivityProblemsRequest.ClientIp != "" || connectivityProblemsRequest.PacketType != "" || connectivityProblemsRequest.IpVersion != "" { 415 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 416 | } 417 | *connectivityProblemsRequest = ConnectivityProblemsRequest{} 418 | ByteArrayToStruct(validator.jsonData, &connectivityProblemsRequest) 419 | return 420 | } 421 | 422 | if len(args) != 1 { 423 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 1, len(args)), CmdErrExitCode) 424 | } 425 | 426 | validator.validateUrlArg(args[0], "url") 427 | 428 | connectivityProblemsRequest.Url = args[0] 429 | 430 | validator.validateIpFlag(connectivityProblemsRequest.SpoofEdgeIp, "edgeServerIp", false) 431 | validator.validateIpFlag(connectivityProblemsRequest.ClientIp, "clientIp", false) 432 | validator.ValidatePortFlag(portStr, &connectivityProblemsRequest.Port, "port", false) 433 | validator.ValidatePacketTypeFlag(&connectivityProblemsRequest.PacketType, "packetType", false) 434 | validator.ValidateIpVersionFlag(&connectivityProblemsRequest.IpVersion, "ipVersion", false) 435 | 436 | } 437 | 438 | func (validator Validator) ValidateGrepFields(args []string, grepRequest *GrepRequest, errorStatusCodeFlag, clientRequest, forwardRequest bool, httpStatusCodes []string, arls []string) { 439 | 440 | if validator.jsonData != nil { 441 | if len(args) > 0 || len(grepRequest.CpCodes) > 0 || len(grepRequest.Hostnames) > 0 || len(grepRequest.ClientIps) > 0 || len(httpStatusCodes) > 0 || len(arls) > 0 || 442 | len(grepRequest.UserAgents) > 0 || errorStatusCodeFlag || forwardRequest || !clientRequest { 443 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 444 | } 445 | *grepRequest = GrepRequest{} 446 | ByteArrayToStruct(validator.jsonData, grepRequest) 447 | return 448 | } 449 | 450 | if len(args) != 3 { 451 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 3, len(args)), CmdErrExitCode) 452 | } 453 | 454 | if len(grepRequest.CpCodes) == 0 && len(grepRequest.Hostnames) == 0 { 455 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Missing, "any"), CmdErrExitCode) 456 | } else if len(grepRequest.CpCodes) != 0 && len(grepRequest.Hostnames) != 0 { 457 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "exclusive"), CmdErrExitCode) 458 | } 459 | 460 | if len(httpStatusCodes) != 0 && errorStatusCodeFlag { 461 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "exclusiveHttpStatusCode"), CmdErrExitCode) 462 | } 463 | 464 | validator.validateIpArg(args[0], "edgeServerIp") 465 | grepRequest.EdgeIp = args[0] 466 | grepRequest.Start = args[1] 467 | grepRequest.End = args[2] 468 | 469 | for _, clientIp := range grepRequest.ClientIps { 470 | if !isValidIp(clientIp) { 471 | AbortWithUsageAndMessage(validator.cmd, GetErrorMessageForFlag(validator.cmd, Invalid, "clientIp"), CmdErrExitCode) 472 | } 473 | } 474 | 475 | if clientRequest && forwardRequest { 476 | grepRequest.LogType = "BOTH" 477 | } else if forwardRequest { 478 | grepRequest.LogType = "F" 479 | } else { 480 | grepRequest.LogType = "R" 481 | } 482 | 483 | if len(httpStatusCodes) > 0 { 484 | grepRequest.HttpStatusCodes = &HttpStatusCode{Comparison: "EQUALS", Value: httpStatusCodes} 485 | } else if errorStatusCodeFlag { 486 | grepRequest.HttpStatusCodes = &HttpStatusCode{Comparison: "NOT_EQUALS", Value: []string{"200"}} 487 | } 488 | 489 | if len(arls) > 0 { 490 | grepRequest.Arls = &Arl{Comparison: "CONTAINS", Value: arls} 491 | } 492 | 493 | } 494 | 495 | func (validator Validator) ValidateTranslateErrorFields(args []string, errorTranslatorRequest *ErrorTranslatorRequest, traceForwardLogs bool) { 496 | 497 | if validator.jsonData != nil { 498 | if len(args) > 0 { 499 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 500 | } 501 | ByteArrayToStruct(validator.jsonData, errorTranslatorRequest) 502 | return 503 | } 504 | 505 | if len(args) != 1 { 506 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 1, len(args)), CmdErrExitCode) 507 | } 508 | errorTranslatorRequest.ErrorCode = args[0] 509 | errorTranslatorRequest.TraceForwardLogs = traceForwardLogs 510 | 511 | } 512 | 513 | func (validator Validator) ValidateContentProblemsFields(args []string, contentProblemsRequest *ContentProblemsRequest) { 514 | 515 | if validator.jsonData != nil { 516 | if len(args) > 0 || len(contentProblemsRequest.RequestHeaders) > 0 || contentProblemsRequest.EdgeIp != "" || contentProblemsRequest.IpVersion != "" { 517 | AbortWithUsageAndMessage(validator.cmd, GetGlobalErrorMessage(FieldsNotRequired), CmdErrExitCode) 518 | } 519 | *contentProblemsRequest = ContentProblemsRequest{} 520 | ByteArrayToStruct(validator.jsonData, &contentProblemsRequest) 521 | return 522 | } 523 | 524 | if len(args) != 1 { 525 | AbortWithUsageAndMessage(validator.cmd, fmt.Sprintf(GetGlobalErrorMessage(MissingArgs), 1, len(args)), CmdErrExitCode) 526 | } 527 | 528 | validator.validateUrlArg(args[0], "url") 529 | 530 | contentProblemsRequest.Url = args[0] 531 | 532 | validator.validateIpFlag(contentProblemsRequest.EdgeIp, "edgeServerIp", false) 533 | validator.ValidateIpVersionFlag(&contentProblemsRequest.IpVersion, "ipVersion", false) 534 | 535 | } 536 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/akamai/cli-diagnostics/cmd" 5 | "github.com/akamai/cli-diagnostics/internal" 6 | ) 7 | 8 | var ( 9 | VERSION string = "1.0.0" 10 | ) 11 | 12 | func main() { 13 | internal.InitLoggingConfig() 14 | cmd.Execute(VERSION) 15 | } --------------------------------------------------------------------------------