├── .gitignore ├── NOTICE ├── CONTRIBUTING.md ├── .travis.yml ├── vendor └── vendor.json ├── Makefile ├── README.md ├── LICENSE └── akamai.go /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | release 3 | vendor/github.com 4 | bin 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | akamai-gtm 2 | 3 | Copyright 2015-2016 Comcast Cable Communications Management, LLC 4 | 5 | This product includes software developed at Comcast (http://www.comcast.com/). 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | If you would like to contribute code to this project you can do so through GitHub by forking the repository and sending a pull request. 2 | 3 | Before Comcast accepts your code into the project you must sign the Comcast Contributor License Agreement (CLA). 4 | 5 | If you haven't previously signed a Comcast CLA, we can email you a PDF that you can sign and scan back to us. Please send us an email or create a new GitHub issue to request a PDF version of the CLA. 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11 5 | - tip 6 | 7 | script: 8 | - make 9 | 10 | matrix: 11 | allow_failures: 12 | - go: tip 13 | 14 | deploy: 15 | provider: releases 16 | api_key: 17 | secure: "hSgFzUg4BAOqG2NeK6cLFVc6aQj1qTjvSxaoj7jPwTLpWfrg+pvAgMjZKJXHV1a/Mp4jH231IcnbdO2hm0ZH77YczLFBP1OhViAFq89wgfDUjRHJ20cfk9QA4DAkO30TKm1tdtnJoyzf3TyVvZsUKb0nc9MN6nRg3JQqGvCb2+FzbOFm7PWPV7zmHU9Io9qXQ4uEx3d/HiRv5rZgo5+eqm44apgbzztEXNWI8wyri7bUNNS0dheDaJAXFxCXh/nyR8ILEOMf8NW53V8gcPA0lHnmdR4M3E0JVZE3QSqMwXObMtrpFP2BBROcfT+q9oNGH36kCPnFrtB+onDMYOzuNhGshu3pYCkXm8FI50U2Y8J9pgJyOkgKYons2JQUXYwzSOtp6NXdyk2t5UvnQABU767/4hQMLQeue0YlnARqUpVkO92XNItfpbSW/ZIpnsGzF0q4LriPSlgzpzwCRl2qUISNKKbo/BM3HfFhOtt0CP9vEmCNoPPV99xG6k9OUNaIArqXNRGqWjK9grYBYKEBxnqDPQ7A4wzPU3L+kt+qjx2MsPGCLcFynhwywR3uq/sE7AKKaimHF2qe/AACUgWuq4srHdTDRrcCMxZFtdrtH85oGrAghAzRAPeHYqZm0fgbuGWrnYd7Kc0Rb53J1SsfEeEKBqUWNifFJsOPn8CwXv8=" 18 | skip_cleanup: true 19 | file_glob: true 20 | file: release/akamai-gtm_* 21 | on: 22 | repo: Comcast/akamai-gtm 23 | tags: true 24 | 25 | notifications: 26 | email: 27 | on_success: never 28 | on_failure: always 29 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "hJFz7fRtr8IIItA2pJwwsyuRuHQ=", 7 | "path": "github.com/comcast/go-edgegrid/edgegrid", 8 | "revision": "e03bc9d8e9ed7b1ba67735ac32464c9f77005d58", 9 | "revisionTime": "2016-11-15T02:41:37Z" 10 | }, 11 | { 12 | "checksumSHA1": "DdH3xAkzAWJ4B/LGYJyCeRsly2I=", 13 | "path": "github.com/mattn/go-runewidth", 14 | "revision": "d6bea18f789704b5f83375793155289da36a3c7f", 15 | "revisionTime": "2016-03-15T04:07:12Z" 16 | }, 17 | { 18 | "checksumSHA1": "SpK8YNVt9qqOzQK1y6EfpO02TlM=", 19 | "path": "github.com/olekukonko/tablewriter", 20 | "revision": "65fec0d89a572b4367094e2058d3ebe667de3b60", 21 | "revisionTime": "2017-12-03T15:10:07Z" 22 | }, 23 | { 24 | "checksumSHA1": "cKNzpMpci3WUAzXpe+tVnBv2cjE=", 25 | "path": "github.com/satori/go.uuid", 26 | "revision": "f9ab0dce87d815821e221626b772e3475a0d2749", 27 | "revisionTime": "2016-03-24T11:22:44Z" 28 | }, 29 | { 30 | "checksumSHA1": "QptO0AepiRNzesHimvUfeXmJp/c=", 31 | "path": "github.com/urfave/cli", 32 | "revision": "39908eb08fee7c10d842622a114a5c133fb0a3c6", 33 | "revisionTime": "2017-12-12T16:34:29Z" 34 | } 35 | ], 36 | "rootPath": "github.com/Comcast/akamai-gtm" 37 | } 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=akamai-gtm 2 | VERSION=0.0.3 3 | TAG=v$(VERSION) 4 | ARCH=$(shell uname -m) 5 | PREFIX=/usr/local 6 | 7 | all: build 8 | 9 | updatedeps: 10 | go get -u golang.org/x/lint/golint 11 | go get -u github.com/kardianos/govendor 12 | govendor sync 13 | 14 | install: build 15 | mkdir -p $(PREFIX)/bin 16 | cp -v bin/$(NAME) $(PREFIX)/bin/$(NAME) 17 | 18 | build: updatedeps vet 19 | go build -ldflags "-X main.version=$(VERSION)" -o bin/$(NAME) 20 | 21 | build_releases: updatedeps 22 | mkdir -p build/Linux && GOOS=linux go build -ldflags "-X main.version=$(VERSION)" -o build/Linux/$(NAME) 23 | mkdir -p build/Darwin && GOOS=darwin go build -ldflags "-X main.version=$(VERSION)" -o build/Darwin/$(NAME) 24 | rm -rf release && mkdir release 25 | tar -zcf release/$(NAME)_$(VERSION)_linux_$(ARCH).tgz -C build/Linux $(NAME) 26 | tar -zcf release/$(NAME)_$(VERSION)_darwin_$(ARCH).tgz -C build/Darwin $(NAME) 27 | 28 | release: build_releases 29 | go get github.com/aktau/github-release 30 | github-release release \ 31 | --user comcast \ 32 | --repo akamai-gtm \ 33 | --tag $(TAG) \ 34 | --name "$(TAG)" \ 35 | --description "akamai-gtm version $(VERSION)" 36 | ls release/*.tgz | xargs -I FILE github-release upload \ 37 | --user comcast \ 38 | --repo akamai-gtm \ 39 | --tag $(TAG) \ 40 | --name FILE \ 41 | --file FILE 42 | 43 | # NOTE: TravisCI will auto-deploy a GitHub release when a tag is pushed 44 | tag: 45 | git tag $(TAG) 46 | git push origin $(TAG) 47 | 48 | lint: updatedeps 49 | golint -set_exit_status 50 | 51 | vet: 52 | go vet 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Comcast/akamai-gtm.svg?branch=master)](https://travis-ci.org/Comcast/akamai-gtm) 2 | 3 | # akamai-gtm 4 | 5 | A Golang-based CLI for Akamai [GTM](https://developer.akamai.com/api/luna/config-gtm/overview.html). 6 | 7 | ## Installation 8 | 9 | Download the desired [release](https://github.com/Comcast/akamai-gtm/releases) version for your operating system. Untar and install the `akamai-gtm` executable 10 | to your `$PATH`. 11 | 12 | ### Compiling from Golang source 13 | 14 | Alternatively, if you choose to compile from Golang source code: 15 | 16 | * install Golang 17 | * set up your `$GOPATH` 18 | * clone `comcast/akamai-gtm` to `$GOPATH/src/github.com/comcast/akamai-gtm` 19 | * cd `$GOPATH/src/github.com/comcast/akamai-gtm && make install` 20 | 21 | ## Usage 22 | 23 | ``` 24 | akamai-gtm --help 25 | 26 | NAME: 27 | akamai-gtm - A CLI to Akamai GTM configuration 28 | 29 | USAGE: 30 | akamai-gtm [global options] command [command options] [arguments...] 31 | 32 | VERSION: 33 | 0.0.1 34 | 35 | COMMANDS: 36 | domains domains 37 | domain domain 38 | domain-create domain-create --type 39 | domain-update domain-update --json 40 | data-centers data-centers 41 | data-centers-delete data-centers-celete --id --id 42 | data-centers-delete-all data-centers-delete-all 43 | data-center data-center --id 44 | data-center-create data-center-create --json 45 | data-center-update data-center-update --json 46 | data-center-delete data-center-delete --id 47 | properties properties 48 | properties-delete properties-delete --names , 49 | properties-delete-all properties-delete-all 50 | property property --name 51 | property-create property-create --json 52 | property-update property-update --json 53 | property-delete property-delete --name 54 | traffic-targets traffic-targets --name 55 | liveness-tests liveness-tests --name 56 | status status 57 | 58 | GLOBAL OPTIONS: 59 | --host value Luna API Hostname [$AKAMAI_EDGEGRID_HOST] 60 | --client_token value, --ct value Luna API Client Token [$AKAMAI_EDGEGRID_CLIENT_TOKEN] 61 | --access_token value, --at value Luna API Access Token [$AKAMAI_EDGEGRID_ACCESS_TOKEN] 62 | --client_secret value, -s value Luna API Client Secret [$AKAMAI_EDGEGRID_CLIENT_SECRET] 63 | --help, -h show help 64 | --version, -v print the version 65 | ``` 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /akamai.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "sort" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/comcast/go-edgegrid/edgegrid" 13 | "github.com/olekukonko/tablewriter" 14 | "github.com/urfave/cli" 15 | ) 16 | 17 | // passed in via Makefile 18 | var version string 19 | 20 | func main() { 21 | app := cli.NewApp() 22 | app.Name = "akamai-gtm" 23 | app.Version = version 24 | app.Usage = "A CLI to Akamai GTM configuration" 25 | app.Flags = []cli.Flag{ 26 | cli.StringFlag{ 27 | Name: "host", 28 | Usage: "Luna API Hostname", 29 | EnvVar: "AKAMAI_EDGEGRID_HOST", 30 | }, 31 | cli.StringFlag{ 32 | Name: "client_token, ct", 33 | Usage: "Luna API Client Token", 34 | EnvVar: "AKAMAI_EDGEGRID_CLIENT_TOKEN", 35 | }, 36 | cli.StringFlag{ 37 | Name: "access_token, at", 38 | Usage: "Luna API Access Token", 39 | EnvVar: "AKAMAI_EDGEGRID_ACCESS_TOKEN", 40 | }, 41 | cli.StringFlag{ 42 | Name: "client_secret, s", 43 | Usage: "Luna API Client Secret", 44 | EnvVar: "AKAMAI_EDGEGRID_CLIENT_SECRET", 45 | }, 46 | } 47 | app.Commands = []cli.Command{ 48 | { 49 | Name: "domains", 50 | Usage: "domains", 51 | Description: "List all GTM Domains", 52 | Action: domains, 53 | }, 54 | { 55 | Name: "domain", 56 | Usage: "domain ", 57 | Description: "View the details of a Domain", 58 | Action: domain, 59 | }, 60 | { 61 | Name: "domain-create", 62 | Usage: "domain-create --type ", 63 | Description: "Create a Domain", 64 | Flags: []cli.Flag{ 65 | cli.StringFlag{ 66 | Name: "type", 67 | Usage: "The Domain type", 68 | }, 69 | }, 70 | Action: domainCreate, 71 | }, 72 | { 73 | Name: "domain-update", 74 | Usage: "domain-update --json ", 75 | Description: "Update a Domain", 76 | Flags: []cli.Flag{ 77 | cli.StringFlag{ 78 | Name: "json", 79 | Usage: "The path to a JSON file", 80 | }, 81 | }, 82 | Action: domainUpdate, 83 | }, 84 | { 85 | Name: "data-centers", 86 | Usage: "data-centers ", 87 | Description: "List all DataCenters associated with a Domain", 88 | Action: dataCenters, 89 | }, 90 | { 91 | Name: "data-centers-delete", 92 | Usage: "data-centers-delete --id --id ", 93 | Description: "Deletes specified DataCenters associated with a Domain", 94 | Flags: []cli.Flag{ 95 | cli.IntSliceFlag{ 96 | Name: "id", 97 | Usage: "--id --id ", 98 | }, 99 | }, 100 | Action: dataCentersDelete, 101 | }, 102 | { 103 | Name: "data-centers-delete-all", 104 | Usage: "data-centers-delete-all ", 105 | Description: "Deletes ALL DataCenters associated with a Domain", 106 | Action: dataCentersDeleteAll, 107 | }, 108 | { 109 | Name: "data-center", 110 | Usage: "data-center --id ", 111 | Description: "View the details of a DataCenter associated with a Domain", 112 | Flags: []cli.Flag{ 113 | cli.StringFlag{ 114 | Name: "id", 115 | Usage: "The data center ID", 116 | }, 117 | }, 118 | Action: dataCenter, 119 | }, 120 | { 121 | Name: "data-center-create", 122 | Usage: "data-center-create --json ", 123 | Description: "Create a DataCenter associated with a Domain from data in a JSON file", 124 | Flags: []cli.Flag{ 125 | cli.StringFlag{ 126 | Name: "json", 127 | Usage: "The path to a JSON file", 128 | }, 129 | }, 130 | Action: dataCenterCreate, 131 | }, 132 | { 133 | Name: "data-center-update", 134 | Usage: "data-center-update --json ", 135 | Description: "Update a DataCenter associated with a Domain from data in a JSON file", 136 | Flags: []cli.Flag{ 137 | cli.StringFlag{ 138 | Name: "json", 139 | Usage: "The path to a JSON file", 140 | }, 141 | }, 142 | Action: dataCenterUpdate, 143 | }, 144 | { 145 | Name: "data-center-delete", 146 | Usage: "data-center-delete --id ", 147 | Description: "Delete a data center", 148 | Flags: []cli.Flag{ 149 | cli.StringFlag{ 150 | Name: "id", 151 | Usage: "The data center ID", 152 | }, 153 | }, 154 | Action: dataCenterDelete, 155 | }, 156 | { 157 | Name: "properties", 158 | Usage: "properties", 159 | Description: "View all Properties of a Domain", 160 | Action: properties, 161 | }, 162 | { 163 | Name: "properties-delete", 164 | Usage: "properties-delete --names , ", 165 | Description: "Deletes specified Properties associated with a Domain", 166 | Flags: []cli.Flag{ 167 | cli.StringFlag{ 168 | Name: "names", 169 | Usage: "A comma-separated list of names of Properties to delete", 170 | }, 171 | }, 172 | Action: propertiesDelete, 173 | }, 174 | { 175 | Name: "properties-delete-all", 176 | Usage: "properties-delete-all ", 177 | Description: "Deletes ALL Properties associated with a Domain", 178 | Action: propertiesDeleteAll, 179 | }, 180 | { 181 | Name: "property", 182 | Usage: "property --name ", 183 | Description: "View the details of a Property", 184 | Flags: []cli.Flag{ 185 | cli.StringFlag{ 186 | Name: "name", 187 | Usage: "The Property name", 188 | }, 189 | }, 190 | Action: property, 191 | }, 192 | { 193 | Name: "property-create", 194 | Usage: "property-create --json ", 195 | Description: "Create a Property", 196 | Flags: []cli.Flag{ 197 | cli.StringFlag{ 198 | Name: "json", 199 | Usage: "The path to a JSON file", 200 | }, 201 | }, 202 | Action: propertyCreate, 203 | }, 204 | { 205 | Name: "property-update", 206 | Usage: "property-update --json ", 207 | Description: "Update a Property", 208 | Flags: []cli.Flag{ 209 | cli.StringFlag{ 210 | Name: "json", 211 | Usage: "The path to a JSON file", 212 | }, 213 | }, 214 | Action: propertyUpdate, 215 | }, 216 | { 217 | Name: "property-delete", 218 | Usage: "property-delete --name ", 219 | Description: "Delete a Property", 220 | Flags: []cli.Flag{ 221 | cli.StringFlag{ 222 | Name: "name", 223 | Usage: "The Property name", 224 | }, 225 | }, 226 | Action: propertyDelete, 227 | }, 228 | { 229 | Name: "traffic-targets", 230 | Usage: "traffic-targets --name ", 231 | Description: "View traffic targets associated with a property", 232 | Flags: []cli.Flag{ 233 | cli.StringFlag{ 234 | Name: "name", 235 | Usage: "The Property name", 236 | }, 237 | }, 238 | Action: trafficTargets, 239 | }, 240 | { 241 | Name: "liveness-tests", 242 | Usage: "liveness-tests --name ", 243 | Description: "View liveness tests associated with a property", 244 | Flags: []cli.Flag{ 245 | cli.StringFlag{ 246 | Name: "name", 247 | Usage: "The Property name", 248 | }, 249 | }, 250 | Action: livenessTests, 251 | }, 252 | { 253 | Name: "status", 254 | Usage: "status ", 255 | Description: "View the Status details for a Domain", 256 | Action: status, 257 | }, 258 | } 259 | app.RunAndExitOnError() 260 | } 261 | 262 | func domains(c *cli.Context) error { 263 | client := client(c) 264 | domains, err := client.Domains() 265 | if err != nil { 266 | return err 267 | } 268 | for _, domain := range domains { 269 | fmt.Printf("%s\n", domain.Name) 270 | } 271 | 272 | return nil 273 | } 274 | 275 | func domain(c *cli.Context) error { 276 | client := client(c) 277 | domain, err := client.Domain(c.Args().First()) 278 | if err != nil { 279 | return err 280 | } 281 | dcs := []string{} 282 | for _, dc := range domain.Datacenters { 283 | dcs = append(dcs, dc.Nickname) 284 | } 285 | data := [][]string{ 286 | []string{"Name", domain.Name}, 287 | []string{"Type", domain.Type}, 288 | []string{"DataCenters", strings.Join(dcs, ", ")}, 289 | []string{"Status", domain.Status.Message}, 290 | []string{"Propagation Status", domain.Status.PropagationStatus}, 291 | []string{"Last Modified By", domain.LastModifiedBy}, 292 | []string{"Last Modified", domain.LastModified}, 293 | []string{"Modification Comments", domain.ModificationComments}, 294 | } 295 | 296 | printBasicTable(data) 297 | 298 | return nil 299 | } 300 | 301 | func domainCreate(c *cli.Context) error { 302 | client := client(c) 303 | domainResp, err := client.DomainCreate(c.Args().First(), c.String("type")) 304 | if err != nil { 305 | return err 306 | } 307 | 308 | fmt.Printf("Created %s\n", domainResp.Domain.Name) 309 | 310 | return nil 311 | } 312 | 313 | func domainUpdate(c *cli.Context) error { 314 | client := client(c) 315 | domainSt := &edgegrid.Domain{} 316 | data, err := ioutil.ReadFile(c.String("json")) 317 | if err != nil { 318 | return err 319 | } 320 | if err := json.Unmarshal(data, domainSt); err != nil { 321 | return err 322 | } 323 | 324 | domainResp, err := client.DomainUpdate(domainSt) 325 | if err != nil { 326 | return err 327 | } 328 | 329 | fmt.Printf("Updated domain: %s\n", domainResp.Domain.Name) 330 | 331 | return nil 332 | } 333 | 334 | func dataCenters(c *cli.Context) error { 335 | domain := c.Args().First() 336 | client := client(c) 337 | dcs, err := client.DataCenters(domain) 338 | if err != nil { 339 | return err 340 | } 341 | data := [][]string{} 342 | for _, dc := range dcs { 343 | data = append(data, []string{dc.Nickname, strconv.Itoa(dc.DataCenterID)}) 344 | } 345 | 346 | if len(data) != 0 { 347 | printTableWithHeaders([]string{"Nickname", "DataCenter ID"}, data) 348 | } else { 349 | fmt.Printf("No data centers found for domain: %s\n", domain) 350 | } 351 | 352 | return nil 353 | } 354 | 355 | func dataCenter(c *cli.Context) error { 356 | dc, err := client(c).DataCenter(c.Args().First(), c.Int("id")) 357 | if err != nil { 358 | return err 359 | } 360 | data := [][]string{ 361 | []string{"Nickname", dc.Nickname}, 362 | []string{"DataCenterID", strconv.Itoa(dc.DataCenterID)}, 363 | []string{"City", dc.City}, 364 | []string{"CloneOf", strconv.Itoa(dc.CloneOf)}, 365 | []string{"Continent", dc.Continent}, 366 | []string{"Country", dc.Country}, 367 | []string{"Latitude", floatToStr(dc.Latitude)}, 368 | []string{"Longitude", floatToStr(dc.Longitude)}, 369 | []string{"StateOrProvince", dc.StateOrProvince}, 370 | []string{"Virtual", strconv.FormatBool(dc.Virtual)}, 371 | []string{"CloudServerTargeting", strconv.FormatBool(dc.CloudServerTargeting)}, 372 | } 373 | 374 | printBasicTable(data) 375 | 376 | return nil 377 | } 378 | 379 | func dataCenterCreate(c *cli.Context) error { 380 | data := unmarshalDc(c) 381 | dc, err := client(c).DataCenterCreate(c.Args().First(), data) 382 | if err != nil { 383 | return err 384 | } 385 | 386 | fmt.Printf("Created %s\n", dc.DataCenter.Nickname) 387 | 388 | return nil 389 | } 390 | 391 | func dataCenterUpdate(c *cli.Context) error { 392 | data := unmarshalDc(c) 393 | dc, err := client(c).DataCenterUpdate(c.Args().First(), data) 394 | if err != nil { 395 | return err 396 | } 397 | 398 | fmt.Printf("Updated %s\n", dc.DataCenter.Nickname) 399 | 400 | return nil 401 | } 402 | 403 | func unmarshalDc(c *cli.Context) *edgegrid.DataCenter { 404 | dcSt := &edgegrid.DataCenter{} 405 | data, err := ioutil.ReadFile(c.String("json")) 406 | if err != nil { 407 | panic(err) 408 | } 409 | if err := json.Unmarshal(data, dcSt); err != nil { 410 | panic(err) 411 | } 412 | 413 | return dcSt 414 | } 415 | 416 | func dataCenterDelete(c *cli.Context) error { 417 | id := c.Int("id") 418 | err := client(c).DataCenterDelete(c.Args().First(), id) 419 | if err != nil { 420 | return err 421 | } 422 | 423 | fmt.Printf("Deleted data center %d\n", id) 424 | 425 | return nil 426 | } 427 | 428 | func dataCentersDelete(c *cli.Context) error { 429 | ids := c.IntSlice("id") 430 | client := client(c) 431 | domainName := c.Args().First() 432 | 433 | for _, id := range ids { 434 | err := client.DataCenterDelete(domainName, id) 435 | if err != nil { 436 | fmt.Printf("Failed to delete DataCenter: %d\n", id) 437 | fmt.Printf("Error is: %v", err) 438 | 439 | return err 440 | } 441 | 442 | fmt.Printf("Deleted DataCenter: %d\n", id) 443 | } 444 | 445 | return nil 446 | } 447 | 448 | func dataCentersDeleteAll(c *cli.Context) error { 449 | myClient := client(c) 450 | domainName := c.Args().First() 451 | dcs, err := myClient.DataCenters(domainName) 452 | if err != nil { 453 | return err 454 | } 455 | for _, dc := range dcs { 456 | err := myClient.DataCenterDelete(domainName, dc.DataCenterID) 457 | if err != nil { 458 | fmt.Printf("Failed to delete DataCenter: %s\n", dc.Nickname) 459 | fmt.Printf("Error is: %v", err) 460 | 461 | return err 462 | } 463 | 464 | fmt.Printf("Deleted DC: %s (%d)\n", dc.Nickname, dc.DataCenterID) 465 | } 466 | 467 | return nil 468 | } 469 | 470 | // Properties is a Property slice 471 | type Properties []Property 472 | 473 | // Property is an Akamai GTM property 474 | type Property struct { 475 | GtmProperty edgegrid.Property 476 | Product string 477 | } 478 | 479 | func (props Properties) Len() int { 480 | return len(props) 481 | } 482 | 483 | func (props Properties) Less(i, j int) bool { 484 | return props[i].Product < props[j].Product 485 | } 486 | 487 | func (props Properties) Swap(i, j int) { 488 | props[i], props[j] = props[j], props[i] 489 | } 490 | 491 | func buildProps(props *edgegrid.Properties) Properties { 492 | created := Properties{} 493 | 494 | for _, prop := range props.Properties { 495 | splitName := strings.Split(prop.Name, ".") 496 | created = append(created, Property{ 497 | GtmProperty: prop, 498 | Product: splitName[len(splitName)-1], 499 | }) 500 | } 501 | 502 | return created 503 | } 504 | 505 | func properties(c *cli.Context) error { 506 | domain := c.Args().First() 507 | ps, err := client(c).Properties(domain) 508 | if err != nil { 509 | return err 510 | } 511 | 512 | props := buildProps(ps) 513 | sort.Sort(props) 514 | 515 | s := [][]string{} 516 | for _, prop := range props { 517 | s = append(s, []string{ 518 | prop.GtmProperty.Name, 519 | prop.GtmProperty.Type, 520 | strings.Join(targetIds(prop.GtmProperty.TrafficTargets), ", "), 521 | }) 522 | } 523 | 524 | if len(s) != 0 { 525 | printTableWithHeaders([]string{"Name", "Type", "Traffic Targets"}, s) 526 | } else { 527 | fmt.Printf("No properties found for domain: %s\n", domain) 528 | } 529 | 530 | return nil 531 | } 532 | 533 | func property(c *cli.Context) error { 534 | prop, err := client(c).Property(c.Args().First(), c.String("name")) 535 | if err != nil { 536 | return err 537 | } 538 | 539 | printProp(prop) 540 | 541 | return nil 542 | } 543 | 544 | func propertyCreate(c *cli.Context) error { 545 | data := unmarshalProp(c) 546 | prop, err := client(c).PropertyCreate(c.Args().First(), data) 547 | if err != nil { 548 | return err 549 | } 550 | 551 | printProp(prop.Property) 552 | 553 | return nil 554 | } 555 | 556 | func propertyUpdate(c *cli.Context) error { 557 | data := unmarshalProp(c) 558 | prop, err := client(c).PropertyUpdate(c.Args().First(), data) 559 | if err != nil { 560 | return err 561 | } 562 | 563 | printProp(prop.Property) 564 | 565 | return nil 566 | } 567 | 568 | func unmarshalProp(c *cli.Context) *edgegrid.Property { 569 | propSt := &edgegrid.Property{} 570 | data, err := ioutil.ReadFile(c.String("json")) 571 | if err != nil { 572 | panic(err) 573 | } 574 | if err := json.Unmarshal(data, propSt); err != nil { 575 | panic(err) 576 | } 577 | 578 | return propSt 579 | } 580 | 581 | func propertyDelete(c *cli.Context) error { 582 | name := c.String("name") 583 | _, err := client(c).PropertyDelete(c.Args().First(), name) 584 | if err != nil { 585 | return err 586 | } 587 | 588 | fmt.Printf("Deleted property %s\n", name) 589 | 590 | return nil 591 | } 592 | 593 | func trafficTargets(c *cli.Context) error { 594 | name := c.String("name") 595 | prop, err := client(c).Property(c.Args().First(), name) 596 | if err != nil { 597 | return err 598 | } 599 | 600 | for _, target := range prop.TrafficTargets { 601 | fmt.Printf("\nTraffic target\n") 602 | printTrafficTarget(target) 603 | } 604 | 605 | return nil 606 | } 607 | 608 | func printTrafficTarget(target edgegrid.TrafficTarget) error { 609 | data := [][]string{ 610 | []string{"Name", interfaceToStr(target.Name)}, 611 | []string{"DCId", strconv.Itoa(target.DataCenterID)}, 612 | []string{"Enabled", strconv.FormatBool(target.Enabled)}, 613 | []string{"HandoutCname", interfaceToStr(target.HandoutCname)}, 614 | []string{"Servers", strings.Join(target.Servers, ", ")}, 615 | []string{"Weight", floatToStr(target.Weight)}, 616 | } 617 | 618 | printBasicTable(data) 619 | 620 | return nil 621 | } 622 | 623 | func livenessTests(c *cli.Context) error { 624 | prop := c.String("name") 625 | props, err := client(c).Property(c.Args().First(), prop) 626 | if err != nil { 627 | return err 628 | } 629 | 630 | for _, test := range props.LivenessTests { 631 | printLivenessTest(test) 632 | } 633 | 634 | return nil 635 | } 636 | 637 | func printLivenessTest(test edgegrid.LivenessTest) error { 638 | data := [][]string{ 639 | []string{"Name", test.Name}, 640 | []string{"HTTPError3xx", strconv.FormatBool(test.HTTPError3xx)}, 641 | []string{"HTTPError4xx", strconv.FormatBool(test.HTTPError4xx)}, 642 | []string{"HTTPError5xx", strconv.FormatBool(test.HTTPError5xx)}, 643 | []string{"TestInterval", strconv.FormatInt(test.TestInterval, 10)}, 644 | []string{"TestObject", test.TestObject}, 645 | []string{"TestObjectPort", strconv.FormatInt(test.TestObjectPort, 10)}, 646 | []string{"TestObjectProtocol", test.TestObjectProtocol}, 647 | []string{"TestObjectUsername", test.TestObjectUsername}, 648 | []string{"TestObjectPassword", test.TestObjectPassword}, 649 | []string{"TestTimeout", floatToStr(test.TestTimeout)}, 650 | []string{"DisableNonstandardPortWarning", strconv.FormatBool(test.DisableNonstandardPortWarning)}, 651 | []string{"RequestString", test.RequestString}, 652 | []string{"ResponseString", test.ResponseString}, 653 | []string{"SSLClientPrivateKey", test.SSLClientPrivateKey}, 654 | []string{"SSLCertificate", test.SSLCertificate}, 655 | []string{"HostHeader", test.HostHeader}, 656 | } 657 | 658 | printBasicTable(data) 659 | 660 | return nil 661 | } 662 | 663 | func propertiesDelete(c *cli.Context) error { 664 | names := strings.Split(c.String("names"), ",") 665 | client := client(c) 666 | domain := c.Args().First() 667 | 668 | for _, name := range names { 669 | _, err := client.PropertyDelete(domain, strings.TrimSpace(name)) 670 | if err != nil { 671 | fmt.Printf("Failed to delete Property: %s\n", name) 672 | fmt.Printf("Error is: %v", err) 673 | 674 | return err 675 | } 676 | 677 | fmt.Printf("Deleted Property: %s\n", name) 678 | } 679 | 680 | return nil 681 | } 682 | 683 | func propertiesDeleteAll(c *cli.Context) error { 684 | domain := c.Args().First() 685 | client := client(c) 686 | props, err := client.Properties(domain) 687 | if err != nil { 688 | return err 689 | } 690 | 691 | for _, prop := range props.Properties { 692 | _, err := client.PropertyDelete(domain, prop.Name) 693 | if err != nil { 694 | fmt.Printf("Failed to delete Property: %s\n", prop.Name) 695 | fmt.Printf("Error is: %v", err) 696 | } else { 697 | fmt.Printf("Deleted Property: %s\n", prop.Name) 698 | } 699 | } 700 | 701 | return nil 702 | } 703 | 704 | func client(c *cli.Context) *edgegrid.GTMClient { 705 | return edgegrid.GTMClientWithCreds( 706 | c.GlobalString("access_token"), 707 | c.GlobalString("client_token"), 708 | c.GlobalString("client_secret"), 709 | c.GlobalString("host")) 710 | } 711 | 712 | func targetIds(trafficTargets []edgegrid.TrafficTarget) []string { 713 | targets := []string{} 714 | 715 | for _, t := range trafficTargets { 716 | targets = append(targets, strconv.Itoa(t.DataCenterID)) 717 | } 718 | 719 | return targets 720 | } 721 | 722 | func livenessTestNames(livenessTests []edgegrid.LivenessTest) []string { 723 | tests := []string{} 724 | 725 | for _, t := range livenessTests { 726 | tests = append(tests, t.Name) 727 | } 728 | 729 | return tests 730 | } 731 | 732 | func printProp(prop *edgegrid.Property) error { 733 | data := [][]string{ 734 | []string{"BackupCname", prop.BackupCname}, 735 | []string{"BackupIP", prop.BackupIP}, 736 | []string{"BalanceByDownloadScore", strconv.FormatBool(prop.BalanceByDownloadScore)}, 737 | []string{"Cname", prop.Cname}, 738 | []string{"Comments", prop.Comments}, 739 | []string{"DynamicTTL", strconv.Itoa(prop.DynamicTTL)}, 740 | []string{"FailbackDelay", strconv.Itoa(prop.FailbackDelay)}, 741 | []string{"FailoverDelay", strconv.Itoa(prop.FailoverDelay)}, 742 | []string{"HandoutMode", prop.HandoutMode}, 743 | []string{"HealthMax", floatToStr(prop.HealthMax)}, 744 | []string{"HealthMultiplier", floatToStr(prop.HealthMultiplier)}, 745 | []string{"HealthThreshold", floatToStr(prop.HealthThreshold)}, 746 | []string{"Ipv6", strconv.FormatBool(prop.Ipv6)}, 747 | []string{"LastModified", prop.LastModified}, 748 | []string{"LivenessTests", strings.Join(livenessTestNames(prop.LivenessTests), ", ")}, 749 | []string{"LoadImbalancePercentage", floatToStr(prop.LoadImbalancePercentage)}, 750 | []string{"MapName", interfaceToStr(prop.MapName)}, 751 | []string{"MaxUnreachablePenalty", interfaceToStr(prop.MaxUnreachablePenalty)}, 752 | []string{"MxRecords", maxRecsString(prop.MxRecords)}, 753 | []string{"Name", prop.Name}, 754 | []string{"ScoreAggregationType", prop.ScoreAggregationType}, 755 | []string{"StaticTTL", interfaceToStr(prop.StaticTTL)}, 756 | []string{"StickinessBonusConstant", interfaceToStr(prop.StickinessBonusConstant)}, 757 | []string{"StickinessBonusPercentage", interfaceToStr(prop.StickinessBonusPercentage)}, 758 | []string{"TrafficTargets", strings.Join(targetIds(prop.TrafficTargets), ", ")}, 759 | []string{"Type", prop.Type}, 760 | []string{"UnreachableThreshold", interfaceToStr(prop.UnreachableThreshold)}, 761 | []string{"UseComputedTargets", strconv.FormatBool(prop.UseComputedTargets)}, 762 | } 763 | 764 | printBasicTable(data) 765 | 766 | return nil 767 | } 768 | 769 | func status(c *cli.Context) error { 770 | client := client(c) 771 | status, err := client.DomainStatus(c.Args().First()) 772 | if err != nil { 773 | return err 774 | } 775 | data := [][]string{ 776 | []string{"PropagationStatus", status.PropagationStatus}, 777 | []string{"PassingValidation", strconv.FormatBool(status.PassingValidation)}, 778 | []string{"Message", status.Message}, 779 | []string{"ChangeID", status.ChangeID}, 780 | []string{"PropagationStatusDate", status.PropagationStatusDate}, 781 | } 782 | 783 | printBasicTable(data) 784 | 785 | return nil 786 | } 787 | 788 | func printBasicTable(data [][]string) { 789 | table := tablewriter.NewWriter(os.Stdout) 790 | table.AppendBulk(data) 791 | table.SetRowLine(true) 792 | table.Render() 793 | } 794 | 795 | func printTableWithHeaders(headers []string, data [][]string) { 796 | table := tablewriter.NewWriter(os.Stdout) 797 | table.SetHeader(headers) 798 | table.AppendBulk(data) 799 | table.SetRowLine(true) 800 | table.Render() 801 | } 802 | 803 | func floatToStr(input float64) string { 804 | return strconv.FormatFloat(input, 'f', 6, 64) 805 | } 806 | 807 | func maxRecsString(records []interface{}) string { 808 | recs := []string{} 809 | 810 | for _, r := range records { 811 | recs = append(recs, r.(string)) 812 | } 813 | 814 | return strings.Join(recs, ", ") 815 | } 816 | 817 | func interfaceToStr(inter interface{}) string { 818 | if str, ok := inter.(string); ok { 819 | return str 820 | } 821 | if num, ok := inter.(int); ok { 822 | return strconv.Itoa(num) 823 | } 824 | if float, ok := inter.(float64); ok { 825 | return floatToStr(float) 826 | } 827 | 828 | return "" 829 | } 830 | --------------------------------------------------------------------------------