├── .Rbuildignore ├── .gitattributes ├── .github └── workflows │ └── check-standard.yaml ├── .gitignore ├── AzureContainers.Rproj ├── AzureContainers.rxproj ├── AzureContainers.sln ├── CONTRIBUTING.md ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── AzureContainers.R ├── add_aci_methods.R ├── add_acr_methods.R ├── add_aks_methods.R ├── add_vmsize_methods.R ├── az_agent_pool.R ├── az_container_instance.R ├── az_container_registry.R ├── az_kubernetes_service.R ├── docker_registry.R ├── ext_tools.R ├── graph_login.R ├── is.R └── kubernetes_cluster.R ├── README.md ├── SECURITY.md ├── man ├── DockerRegistry.Rd ├── KubernetesCluster.Rd ├── aci.Rd ├── aci_utils.Rd ├── acr.Rd ├── agent_pool.Rd ├── aks.Rd ├── aks_pools.Rd ├── call_docker.Rd ├── call_docker_compose.Rd ├── call_helm.Rd ├── call_kubectl.Rd ├── create_aci.Rd ├── create_acr.Rd ├── create_aks.Rd ├── delete_aci.Rd ├── delete_acr.Rd ├── delete_aks.Rd ├── docker_registry.Rd ├── figures │ └── logo.png ├── get_aci.Rd ├── get_acr.Rd ├── get_aks.Rd ├── is.Rd ├── kubernetes_cluster.Rd ├── list_kubernetes_versions.Rd └── list_vm_sizes.Rd ├── tests ├── resources │ ├── hello.yaml │ └── hello_dockerfile ├── testthat.R └── testthat │ ├── setup.R │ ├── test00_tools.R │ ├── test01_acr.R │ ├── test02_aci.R │ ├── test03a_aks_msi.R │ ├── test03b_aks_acr_msi.R │ ├── test03c_aks_sp.R │ ├── test03d_aks_acr_sp.R │ ├── test03e_aks_oldnodes.R │ └── test03f_aks_private.R └── vignettes ├── perms2.png ├── vig01_plumber_deploy.Rmd ├── vig02_restrserve_deploy_aci.Rmd ├── vig03_securing_aks_traefik.Rmd └── vig04_rbac.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^misc$ 2 | ^\.vs$ 3 | \.sln$ 4 | \.Rproj$ 5 | \.Rxproj$ 6 | ^\.Rproj\.user$ 7 | CONTRIBUTING.md 8 | SECURITY.md 9 | drat.sh 10 | .travis.yml 11 | ^LICENSE\.md$ 12 | ^\.github$ 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/check-standard.yaml: -------------------------------------------------------------------------------- 1 | # For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. 2 | # https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions 3 | on: [push, pull_request] 4 | 5 | name: R-CMD-check 6 | 7 | jobs: 8 | R-CMD-check: 9 | if: github.repository_owner != 'cloudyr' 10 | runs-on: ${{ matrix.config.os }} 11 | 12 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | config: 18 | - {os: windows-latest, r: 'release'} 19 | - {os: macOS-latest, r: 'release'} 20 | - {os: ubuntu-24.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} 21 | 22 | env: 23 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 24 | RSPM: ${{ matrix.config.rspm }} 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 # required for mirroring, see https://stackoverflow.com/a/64272409/474349 30 | 31 | - name: Copy to Cloudyr 32 | if: github.repository_owner == 'Azure' && runner.os == 'Linux' && github.ref == 'refs/heads/master' 33 | env: 34 | token: "${{ secrets.ghPat }}" 35 | # git config hack required, see https://stackoverflow.com/q/64270867/474349 36 | run: | 37 | export CLOUDYR_REPO=$(echo $GITHUB_REPOSITORY | sed "s/Azure/cloudyr/") 38 | git config -l | grep 'http\..*\.extraheader' | cut -d= -f1 | \ 39 | xargs -L1 git config --unset-all 40 | git push --prune https://token:$token@github.com/${CLOUDYR_REPO}.git +refs/remotes/origin/*:refs/heads/* +refs/tags/*:refs/tags/* 41 | 42 | - uses: r-lib/actions/setup-r@v2 43 | with: 44 | r-version: ${{ matrix.config.r }} 45 | 46 | - uses: r-lib/actions/setup-pandoc@v2 47 | 48 | - name: Query dependencies 49 | run: | 50 | install.packages('remotes') 51 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 52 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 53 | shell: Rscript {0} 54 | 55 | - name: Cache R packages 56 | if: runner.os != 'Windows' 57 | uses: actions/cache@v3 58 | with: 59 | path: ${{ env.R_LIBS_USER }} 60 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 61 | # restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-5- 62 | 63 | - name: Install system dependencies 64 | if: runner.os == 'Linux' 65 | run: | 66 | while read -r cmd 67 | do 68 | eval sudo $cmd 69 | done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') 70 | 71 | - name: Install dependencies 72 | run: | 73 | remotes::install_deps(dependencies = TRUE) 74 | remotes::install_cran(c("pkgbuild", "rcmdcheck", "drat")) 75 | shell: Rscript {0} 76 | 77 | - name: Check 78 | env: 79 | _R_CHECK_CRAN_INCOMING_REMOTE_: false 80 | _R_CHECK_FORCE_SUGGESTS_: false 81 | run: | 82 | pkg <- pkgbuild::build() 83 | rcmdcheck::rcmdcheck(pkg, args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") 84 | shell: Rscript {0} 85 | 86 | - name: Upload check results 87 | if: failure() 88 | uses: actions/upload-artifact@v4 89 | with: 90 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 91 | path: check 92 | 93 | - name: Update Cloudyr drat 94 | if: success() && github.repository_owner == 'Azure' && runner.os == 'Linux' && github.ref == 'refs/heads/master' 95 | env: 96 | token: "${{ secrets.ghPat }}" 97 | run: | 98 | cd .. 99 | export PKGBUILD_GZ=$(ls *.gz) 100 | mkdir drat 101 | cd drat 102 | git init 103 | git config user.email "dummy@example.com" 104 | git config user.name "Github Actions" 105 | git remote add upstream "https://token:$token@github.com/cloudyr/cloudyr.github.io.git" 106 | git fetch upstream 107 | git checkout master 108 | Rscript -e "drat::insertPackage('../$PKGBUILD_GZ', repodir='./drat')" 109 | git add --all 110 | git commit -m "add $PKGBUILD_GZ (build $GITHUB_RUN_NUMBER)" 111 | git push 112 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | .RHistory 264 | misc/ 265 | .Rproj.user 266 | -------------------------------------------------------------------------------- /AzureContainers.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /AzureContainers.rxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 932c3bb9-b40d-4a3f-b998-6cf462d161f3 5 | 6 | 7 | 15.0 8 | Debug 9 | AnyCPU 10 | 11 | 12 | Script.R 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /AzureContainers.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2003 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{DA7A21FA-8162-4350-AD77-A8D1B671F3ED}") = "AzureContainers", "AzureContainers.rxproj", "{932C3BB9-B40D-4A3F-B998-6CF462D161F3}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {932C3BB9-B40D-4A3F-B998-6CF462D161F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {932C3BB9-B40D-4A3F-B998-6CF462D161F3}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {932C3BB9-B40D-4A3F-B998-6CF462D161F3}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {932C3BB9-B40D-4A3F-B998-6CF462D161F3}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {C0488356-7D4E-41B2-88EC-67B303F2E8BB} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.microsoft.com. 6 | 7 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: AzureContainers 2 | Title: Interface to 'Container Instances', 'Docker Registry' and 'Kubernetes' in 'Azure' 3 | Version: 1.3.3 4 | Authors@R: c( 5 | person("Hong", "Ooi", , "hongooi73@gmail.com", role = c("aut", "cre")), 6 | person("Bill", "Liang", role = "ctb", comment = "Assistance debugging MMLS on Kubernetes"), 7 | person("Ramkumar", "Chandrasekaran", role = "ctb", comment = "Original blog article on Dockerising MMLS"), 8 | person("Microsoft", role="cph") 9 | ) 10 | Description: An interface to container functionality in Microsoft's 'Azure' cloud: . Manage 'Azure Container Instance' (ACI), 'Azure Container Registry' (ACR) and 'Azure Kubernetes Service' (AKS) resources, push and pull images, and deploy services. On the client side, lightweight shells to the 'docker', 'docker-compose', 'kubectl' and 'helm' commandline tools are provided. Part of the 'AzureR' family of packages. 11 | URL: https://github.com/Azure/AzureContainers https://github.com/Azure/AzureR 12 | BugReports: https://github.com/Azure/AzureContainers/issues 13 | License: MIT + file LICENSE 14 | VignetteBuilder: knitr 15 | Depends: 16 | R (>= 3.3) 17 | Imports: 18 | utils, 19 | AzureRMR (>= 2.0.0), 20 | AzureGraph (>= 1.1.0), 21 | openssl, 22 | httr, 23 | R6, 24 | processx 25 | Suggests: 26 | knitr, 27 | rmarkdown, 28 | testthat, 29 | uuid, 30 | MASS, 31 | bcrypt, 32 | randomForest, 33 | plumber, 34 | RestRserve, 35 | AzureKeyVault 36 | Roxygen: list(markdown=TRUE, r6=FALSE, old_usage=TRUE) 37 | RoxygenNote: 7.3.1 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2018 2 | COPYRIGHT HOLDER: Microsoft 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2018 Microsoft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(DockerRegistry) 4 | export(KubernetesCluster) 5 | export(aci) 6 | export(aci_creds) 7 | export(aci_ports) 8 | export(acr) 9 | export(agent_pool) 10 | export(aks) 11 | export(aks_pools) 12 | export(call_docker) 13 | export(call_docker_compose) 14 | export(call_helm) 15 | export(call_kubectl) 16 | export(docker_registry) 17 | export(get_aci_credentials_list) 18 | export(is_aci) 19 | export(is_acr) 20 | export(is_aks) 21 | export(is_docker_registry) 22 | export(is_kubernetes_cluster) 23 | export(kubernetes_cluster) 24 | import(AzureRMR) 25 | importFrom(utils,tail) 26 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # AzureContainers 1.3.3 2 | 3 | - Updated web links for CRAN 4 | 5 | # AzureContainers 1.3.2 6 | 7 | - Add a `secure_env_vars` argument to the `create_aci()` method, to set secure environment variables in the instance. The values of these variables are not visible in the container's properties, eg when viewed in the Azure portal or via the CLI. 8 | - Fix a bug in setting the environment variables for ACI (#16). 9 | 10 | # AzureContainers 1.3.1 11 | 12 | - Change maintainer email address. 13 | 14 | # AzureContainers 1.3.0 15 | 16 | - Significant enhancements for AKS: 17 | - Fully support creating clusters with managed identities. This is recommended and the new default, compared to the older method of using service principals to control cluster resources. 18 | - Support creating clusters using VM scalesets for the cluster nodes. This is recommended and the new default, compared to using individual VMs. 19 | - Support private clusters. 20 | - Support node autoscaling for agent pools backed by VM scalesets. 21 | - Support spot (low-priority) nodes for agent pools backed by VM scalesets. 22 | - New methods for the `az_kubernetes_service` class, for managing agent pools: `get_agent_pool`, `create_agent_pool`, `delete_agent_pool` and `list_agent_pools`. Creating new agent pools requires VM scalesets, as mentioned above. 23 | - New `agent_pool` function to supply the parameters for a _single_ AKS agent pool. 24 | - The functions to call external tools (`call_docker`, `call_docker_compose`, `call_kubernetes` and `call_helm`) now use the value of the system option `azure_containers_tool_echo` to determine whether to echo output to the screen. If this is unset, the fallback is `TRUE` (as in previous versions). 25 | - Remove MMLS vignette; version 9.3.0 is now very old. 26 | - New vignettes on securing an ACI deployment with RestRserve, and deploying a secured service on AKS with Traefik/Let's Encrypt. 27 | - The utility functions `call_docker`, `call_docker_compose`, `call_kubectl` and `call_helm` now accept a vector of individual commandline options as the first argument, which is the format expected by `processx::run`. You can still provide the full commandline as a single string but this is discouraged, as it will likely fail for things like paths containing spaces. 28 | 29 | # AzureContainers 1.2.1 30 | 31 | - Fix a bug where `call_docker_compose` could be checking for the wrong binary. 32 | 33 | # AzureContainers 1.2.0 34 | 35 | - New `call_docker_compose` function for calling docker-compose. 36 | - Add delay in loop to wait for service principal during AKS resource creation; could timeout prematurely otherwise. 37 | - `KubernetesCluster$create()`, `apply()`, etc now accept HTTP\[S\] URLs as well as filenames as arguments. 38 | - Use the processx package to run external commands, rather than `base::system2()`. A major benefit of this change is that command output is automatically captured and returned as an R object, making it easier to write automated scripts. 39 | - The commandline is now a list component of the R object, rather than an attribute. 40 | - The various `DockerRegistry` and `KubernetesCluster` methods for calling docker, kubectl and helm now have `...` as an argument, allowing you to pass extra inputs to these commands as needed. 41 | - Add `list_cluster_resources()` method for the AKS resource class, which returns a list of all the Azure resources managed by the cluster. 42 | 43 | # AzureContainers 1.1.2 44 | 45 | * The `aks$update_aad_password()` and `aks$update_service_password()` methods now use the new Graph API calls for managing app passwords. The arguments to both these methods are `name` (an optional friendly name for the password) and `duration`. As a security measure, passwords can no longer be manually specified; instead all passwords are now auto-generated on the server with a cryptographically secure PRNG. 46 | 47 | # AzureContainers 1.1.1 48 | 49 | * Enable creating ACI and AKS instances with assigned managed identities. Note that this is still in preview for AKS; see the [Microsoft Docs page](https://learn.microsoft.com/en-us/azure/aks/use-managed-identity) for enabling this feature. 50 | 51 | # AzureContainers 1.1.0 52 | 53 | * Make `docker_registry` and `kubernetes_cluster` into constructor functions rather than R6 classes, for consistency with other AzureR packages. The corresponding class objects are now `DockerRegistry` and `KubernetesCluster`. 54 | * Enable AAD authentication for ACR. By default, instantiating a new docker registry object will authenticate using the AAD credentials of the currently signed-in user. Alternative authentication details can be supplied to `docker_registry`, which will be passed to `AzureAuth::get_azure_token`. See the help for `docker_registry` for more information. 55 | * Enable authenticating with service principals to ACR from ACI and AKS. 56 | * By default, create new container instances with a managed service identity. 57 | * Add `aks$update_aad_password()` method to reset/update the password for AAD integration. 58 | * Add custom `acr$add_role_assignment()` method that recognises AKS objects. 59 | 60 | # AzureContainers 1.0.3 61 | 62 | * Add `aks$update_service_password()` method to reset/update the service principal credentials. 63 | * Send the docker password via `stdin`, rather than on the commandline. 64 | * Not released to CRAN (superseded by 1.1.0 above). 65 | 66 | # AzureContainers 1.0.2 67 | 68 | * Ensure dir for Kubernetes config file exists before writing the file. 69 | * Add `wait` argument to `create_aci` and `create_aks` methods; rely on AzureRMR 2.0 for implementation. 70 | * By default, create a new service principal when creating a new AKS resource; this relies on the AzureGraph package. 71 | * Fix bug in `aci$start()` method. 72 | * By default, save the config file for an AKS cluster in the AzureR directory to allow reuse without going through Resource Manager. 73 | 74 | # AzureContainers 1.0.1 75 | 76 | * Change `aks$get_cluster()` method to use a non-deprecated API call. 77 | * Allow resource group and subscription accessor methods to work even if AzureContainers is not on the search path. 78 | * Allow for different AAD token implementations, either from httr or AzureAuth. 79 | 80 | # AzureContainers 1.0.0 81 | 82 | * Submitted to CRAN 83 | 84 | # AzureContainers 0.9.0 85 | 86 | * Moved to cloudyr organisation 87 | -------------------------------------------------------------------------------- /R/AzureContainers.R: -------------------------------------------------------------------------------- 1 | #' @import AzureRMR 2 | #' @importFrom utils tail 3 | NULL 4 | 5 | .AzureContainers <- new.env() 6 | 7 | .az_cli_app_id <- "04b07795-8ddb-461a-bbee-02f9e1bf7b46" 8 | 9 | globalVariables("self", "AzureContainers") 10 | 11 | .onLoad <- function(libname, pkgname) 12 | { 13 | # find docker, kubectl and helm binaries 14 | .AzureContainers$docker <- Sys.which("docker") 15 | .AzureContainers$dockercompose <- Sys.which("docker-compose") 16 | .AzureContainers$kubectl <- Sys.which("kubectl") 17 | .AzureContainers$helm <- Sys.which("helm") 18 | 19 | ## add methods to AzureRMR resource group and subscription classes 20 | add_acr_methods() 21 | add_aks_methods() 22 | add_aci_methods() 23 | add_vmsize_methods() 24 | } 25 | 26 | 27 | .onAttach <- function(libname, pkgname) 28 | { 29 | if(.AzureContainers$docker != "") 30 | packageStartupMessage("Using docker binary ", .AzureContainers$docker) 31 | if(.AzureContainers$dockercompose != "") 32 | packageStartupMessage("Using docker-compose binary ", .AzureContainers$dockercompose) 33 | if(.AzureContainers$kubectl != "") 34 | packageStartupMessage("Using kubectl binary ", .AzureContainers$kubectl) 35 | if(.AzureContainers$helm != "") 36 | packageStartupMessage("Using helm binary ", .AzureContainers$helm) 37 | 38 | if(.AzureContainers$docker == "") 39 | packageStartupMessage("NOTE: docker binary not found") 40 | if(.AzureContainers$dockercompose == "") 41 | packageStartupMessage("NOTE: docker-compose binary not found") 42 | if(.AzureContainers$kubectl == "") 43 | packageStartupMessage("NOTE: kubectl binary not found") 44 | if(.AzureContainers$helm == "") 45 | packageStartupMessage("NOTE: helm binary not found") 46 | invisible(NULL) 47 | } 48 | -------------------------------------------------------------------------------- /R/add_aci_methods.R: -------------------------------------------------------------------------------- 1 | #' Create Azure Container Instance (ACI) 2 | #' 3 | #' Method for the [AzureRMR::az_resource_group] class. 4 | #' 5 | #' @rdname create_aci 6 | #' @name create_aci 7 | #' @aliases create_aci 8 | #' @section Usage: 9 | #' ``` 10 | #' create_aci(name, location = self$location, 11 | #' container = name, image, 12 | #' registry_creds = list(), 13 | #' cores = 1, memory = 8, 14 | #' os = c("Linux", "Windows"), 15 | #' command = list(), env_vars = list(), 16 | #' ports = aci_ports(), dns_name = name, public_ip = TRUE, 17 | #' restart = c("Always", "OnFailure", "Never"), managed_identity = TRUE, 18 | #' ...) 19 | #' ``` 20 | #' @section Arguments: 21 | #' - `name`: The name of the ACI service. 22 | #' - `location`: The location/region in which to create the ACI service. Defaults to this resource group's location. 23 | #' - `container`: The name of the running container. 24 | #' - `image`: The name of the image to run. 25 | #' - `registry_creds`: Docker registry authentication credentials, if the image is stored in a private registry. See 'Details'. 26 | #' - `cores`: The number of CPU cores for the instance. 27 | #' - `memory`: The memory size in GB for the instance. 28 | #' - `os`: The operating system to run in the instance. 29 | #' - `command`: A list of commands to run in the instance. This is similar to the `--entrypoint` commandline argument to `docker run`; see [here](https://learn.microsoft.com/en-us/azure/container-instances/container-instances-start-command) for some examples. 30 | #' - `env_vars`: A list of name-value pairs to set as environment variables in the instance. 31 | #' - `secure_env_vars`: A list of name-value pairs to set as _secure_ environment variables in the instance. The values of these variables are not visible in the container's properties, eg when viewed in the Azure portal or via the CLI. 32 | #' - `ports`: The network ports to open. By default, opens ports 80 and 443. See 'Details'. 33 | #' - `dns_name`: The domain name prefix for the instance. Only takes effect if `public_ip=TRUE`. 34 | #' - `public_ip`: Whether the instance should be publicly accessible. 35 | #' - `restart`: Whether to restart the instance should an event occur. 36 | #' - `managed_identity`: Whether to assign the container instance a managed identity. 37 | #' - `...`: Other named arguments to pass to the [AzureRMR::az_resource] initialization function. 38 | #' 39 | #' @section Details: 40 | #' An ACI resource is a running container hosted in Azure. See the [documentation for the resource](https://learn.microsoft.com/en-us/azure/container-instances/) for more information. Currently ACI only supports a single image in an instance. 41 | #' 42 | #' To supply the registry authentication credentials, the `registry_creds` argument should contain either an [ACR][acr] object, a [docker_registry] object, or the result of a call to the [aci_creds] function. 43 | #' 44 | #' The ports to open should be obtained by calling the [aci_ports] function. This takes a vector of port numbers as well as the protocol (TCP or UDP) for each port. 45 | #' 46 | #' @section Value: 47 | #' An object of class `az_container_instance` representing the instance. 48 | #' 49 | #' @seealso 50 | #' [get_aci], [delete_aci], [list_acis] 51 | #' 52 | #' [az_container_instance] 53 | #' 54 | #' [ACI documentation](https://learn.microsoft.com/en-us/azure/container-instances/) and 55 | #' [API reference](https://learn.microsoft.com/en-us/rest/api/container-instances/) 56 | #' 57 | #' [Docker commandline reference](https://docs.docker.com/engine/reference/commandline/cli/) 58 | #' 59 | #' @examples 60 | #' \dontrun{ 61 | #' 62 | #' rg <- AzureRMR::get_azure_login()$ 63 | #' get_subscription("subscription_id")$ 64 | #' get_resource_group("rgname") 65 | #' 66 | #' # get the ACR resource that contains the image 67 | #' myacr <- rg$get_acr("myregistry", as_admin=TRUE) 68 | #' 69 | #' rg$create_aci("mycontainer", 70 | #' image="myregistry.azurecr.io/myimage:latest", 71 | #' registry_creds=myacr) 72 | #' 73 | #' } 74 | NULL 75 | 76 | 77 | #' Get Azure Container Instance (ACI) 78 | #' 79 | #' Method for the [AzureRMR::az_resource_group] class. 80 | #' 81 | #' @rdname get_aci 82 | #' @name get_aci 83 | #' @aliases get_aci list_acis 84 | #' 85 | #' @section Usage: 86 | #' ``` 87 | #' get_aci(name) 88 | #' list_acis() 89 | #' ``` 90 | #' @section Arguments: 91 | #' - `name`: For `get_aci()`, the name of the container instance resource. 92 | #' 93 | #' @section Details: 94 | #' The `AzureRMR::az_resource_group` class has both `get_aci()` and `list_acis()` methods, while the `AzureRMR::az_subscription` class only has the latter. 95 | #' 96 | #' @section Value: 97 | #' For `get_aci()`, an object of class `az_container_instance` representing the instance resource. 98 | #' 99 | #' For `list_acis()`, a list of such objects. 100 | #' 101 | #' @seealso 102 | #' [create_aci], [delete_aci] 103 | #' 104 | #' [az_container_instance] 105 | #' 106 | #' [ACI documentation](https://learn.microsoft.com/en-us/azure/container-instances/) and 107 | #' [API reference](https://learn.microsoft.com/en-us/rest/api/container-instances/) 108 | #' 109 | #' [Docker commandline reference](https://docs.docker.com/engine/reference/commandline/cli/) 110 | #' 111 | #' @examples 112 | #' \dontrun{ 113 | #' 114 | #' rg <- AzureRMR::get_azure_login()$ 115 | #' get_subscription("subscription_id")$ 116 | #' get_resource_group("rgname") 117 | #' 118 | #' rg$get_aci("mycontainer") 119 | #' 120 | #' } 121 | NULL 122 | 123 | 124 | #' Delete an Azure Container Instance (ACI) 125 | #' 126 | #' Method for the [AzureRMR::az_resource_group] class. 127 | #' 128 | #' @rdname delete_aci 129 | #' @name delete_aci 130 | #' @aliases delete_aci 131 | #' 132 | #' @section Usage: 133 | #' ``` 134 | #' delete_aci(name, confirm=TRUE, wait=FALSE) 135 | #' ``` 136 | #' @section Arguments: 137 | #' - `name`: The name of the container instance. 138 | #' - `confirm`: Whether to ask for confirmation before deleting. 139 | #' - `wait`: Whether to wait until the deletion is complete. 140 | #' 141 | #' @section Value: 142 | #' NULL on successful deletion. 143 | #' 144 | #' @seealso 145 | #' [create_aci], [get_aci] 146 | #' 147 | #' [az_container_instance] 148 | #' 149 | #' [ACI documentation](https://learn.microsoft.com/en-us/azure/container-instances/) and 150 | #' [API reference](https://learn.microsoft.com/en-us/rest/api/container-instances/) 151 | #' 152 | #' [Docker commandline reference](https://docs.docker.com/engine/reference/commandline/cli/) 153 | #' 154 | #' @examples 155 | #' \dontrun{ 156 | #' 157 | #' rg <- AzureRMR::get_azure_login()$ 158 | #' get_subscription("subscription_id")$ 159 | #' get_resource_group("rgname") 160 | #' 161 | #' rg$delete_aci("mycontainer") 162 | #' 163 | #' } 164 | NULL 165 | 166 | 167 | add_aci_methods <- function() 168 | { 169 | az_resource_group$set("public", "create_aci", overwrite=TRUE, 170 | function(name, location=self$location, 171 | container=name, 172 | image, 173 | registry_creds=list(), 174 | cores=1, 175 | memory=8, 176 | os=c("Linux", "Windows"), 177 | command=list(), 178 | env_vars=list(), 179 | secure_env_vars=list(), 180 | ports=aci_ports(), 181 | dns_name=name, 182 | public_ip=TRUE, 183 | restart=c("Always", "OnFailure", "Never"), 184 | managed_identity=TRUE, 185 | ..., 186 | wait=TRUE) 187 | { 188 | as_name_value_pairs <- function(vars, securevars) 189 | { 190 | c( 191 | lapply(seq_along(vars), function(i) list(name=names(vars)[i], value=vars[[i]])), 192 | lapply(seq_along(securevars), function(i) list(name=names(securevars)[i], secureValue=securevars[[i]])) 193 | ) 194 | } 195 | 196 | containers <- list( 197 | name=container, 198 | properties=list( 199 | image=image, 200 | command=I(command), 201 | environmentVariables=as_name_value_pairs(env_vars, secure_env_vars), 202 | resources=list(requests=list(cpu=cores, memoryInGB=memory)), 203 | ports=ports 204 | ) 205 | ) 206 | 207 | props <- list( 208 | containers=list(containers), 209 | restartPolicy=match.arg(restart), 210 | osType=match.arg(os) 211 | ) 212 | 213 | if(!is_empty(registry_creds)) 214 | props$imageRegistryCredentials <- get_aci_credentials_list(registry_creds) 215 | if(public_ip) 216 | props$ipAddress <- list(type="public", dnsNameLabel=dns_name, ports=ports) 217 | 218 | identity <- if(managed_identity) 219 | list(type="systemAssigned") 220 | else NULL 221 | 222 | AzureContainers::aci$new(self$token, self$subscription, self$name, 223 | type="Microsoft.containerInstance/containerGroups", name=name, location=location, 224 | properties=props, 225 | identity=identity, 226 | ..., 227 | wait=wait) 228 | }) 229 | 230 | az_resource_group$set("public", "get_aci", overwrite=TRUE, 231 | function(name) 232 | { 233 | AzureContainers::aci$new(self$token, self$subscription, self$name, 234 | type="Microsoft.containerInstance/containerGroups", name=name) 235 | }) 236 | 237 | az_resource_group$set("public", "delete_aci", overwrite=TRUE, 238 | function(name, confirm=TRUE, wait=FALSE) 239 | { 240 | self$get_aci(name)$delete(confirm=confirm, wait=wait) 241 | }) 242 | 243 | az_resource_group$set("public", "list_acis", overwrite=TRUE, 244 | function() 245 | { 246 | provider <- "Microsoft.ContainerInstance" 247 | path <- "containerGroups" 248 | api_version <- az_subscription$ 249 | new(self$token, self$subscription)$ 250 | get_provider_api_version(provider, path) 251 | 252 | op <- file.path("resourceGroups", self$name, "providers", provider, path) 253 | 254 | cont <- call_azure_rm(self$token, self$subscription, op, api_version=api_version) 255 | lst <- lapply(cont$value, 256 | function(parms) AzureContainers::aci$new(self$token, self$subscription, deployed_properties=parms)) 257 | 258 | # keep going until paging is complete 259 | while(!is_empty(cont$nextLink)) 260 | { 261 | cont <- call_azure_url(self$token, cont$nextLink) 262 | lst <- lapply(cont$value, 263 | function(parms) AzureContainers::aci$new(self$token, self$subscription, deployed_properties=parms)) 264 | } 265 | named_list(lst) 266 | }) 267 | 268 | az_subscription$set("public", "list_acis", overwrite=TRUE, 269 | function() 270 | { 271 | provider <- "Microsoft.ContainerInstance" 272 | path <- "containerGroups" 273 | api_version <- self$get_provider_api_version(provider, path) 274 | 275 | op <- file.path("providers", provider, path) 276 | 277 | cont <- call_azure_rm(self$token, self$id, op, api_version=api_version) 278 | lst <- lapply(cont$value, 279 | function(parms) AzureContainers::aci$new(self$token, self$id, deployed_properties=parms)) 280 | 281 | # keep going until paging is complete 282 | while(!is_empty(cont$nextLink)) 283 | { 284 | cont <- call_azure_url(self$token, cont$nextLink) 285 | lst <- lapply(cont$value, 286 | function(parms) AzureContainers::aci$new(self$token, self$id, deployed_properties=parms)) 287 | } 288 | named_list(lst) 289 | }) 290 | 291 | } 292 | 293 | -------------------------------------------------------------------------------- /R/add_acr_methods.R: -------------------------------------------------------------------------------- 1 | #' Create Azure Container Registry (ACR) 2 | #' 3 | #' Method for the [AzureRMR::az_resource_group] class. 4 | #' 5 | #' @rdname create_acr 6 | #' @name create_acr 7 | #' @aliases create_acr 8 | #' @section Usage: 9 | #' ``` 10 | #' create_acr(name, location = self$location, 11 | #' admin_user_enabled = TRUE, sku = "Standard", ...) 12 | #' ``` 13 | #' @section Arguments: 14 | #' - `name`: The name of the container registry. 15 | #' - `location`: The location/region in which to create the container registry. Defaults to this resource group's location. 16 | #' - `admin_user_enabled`: Whether to enable the Admin user. Currently this must be `TRUE` for ACI to pull from the registry. 17 | #' - `sku`: Either "Basic", "Standard" (the default) or "Premium". 18 | #' - `wait`: Whether to wait until the ACR resource provisioning is complete. 19 | #' - `...`: Other named arguments to pass to the [AzureRMR::az_resource] initialization function. 20 | #' 21 | #' @section Details: 22 | #' An ACR resource is a Docker registry hosted in Azure. See the [documentation for the resource](https://learn.microsoft.com/en-us/azure/container-registry/) for more information. To work with the registry (transfer images, retag images, etc) see the [documentation for the registry endpoint][docker_registry]. 23 | #' 24 | #' @section Value: 25 | #' An object of class `az_container_registry` representing the registry resource. 26 | #' 27 | #' @seealso 28 | #' [get_acr], [delete_acr], [list_acrs] 29 | #' 30 | #' [az_container_registry] 31 | #' 32 | #' [docker_registry] for the registry endpoint 33 | #' 34 | #' [ACR documentation](https://learn.microsoft.com/en-us/azure/container-registry/) and 35 | #' [API reference](https://learn.microsoft.com/en-us/rest/api/containerregistry/registries) 36 | #' 37 | #' [Docker registry API](https://docs.docker.com/registry/spec/api/) 38 | #' 39 | #' @examples 40 | #' \dontrun{ 41 | #' 42 | #' rg <- AzureRMR::get_azure_login()$ 43 | #' get_subscription("subscription_id")$ 44 | #' get_resource_group("rgname") 45 | #' 46 | #' rg$create_acr("myregistry") 47 | #' 48 | #' } 49 | NULL 50 | 51 | 52 | #' Get Azure Container Registry (ACR) 53 | #' 54 | #' Method for the [AzureRMR::az_resource_group] class. 55 | #' 56 | #' @rdname get_acr 57 | #' @name get_acr 58 | #' @aliases get_acr list_acrs 59 | #' 60 | #' @section Usage: 61 | #' ``` 62 | #' get_acr(name) 63 | #' list_acrs() 64 | #' ``` 65 | #' @section Arguments: 66 | #' - `name`: For `get_acr()`, the name of the container registry resource. 67 | #' 68 | #' @section Details: 69 | #' The `AzureRMR::az_resource_group` class has both `get_acr()` and `list_acrs()` methods, while the `AzureRMR::az_subscription` class only has the latter. 70 | #' 71 | #' @section Value: 72 | #' For `get_acr()`, an object of class `az_container_registry` representing the registry resource. 73 | #' 74 | #' For `list_acrs()`, a list of such objects. 75 | #' 76 | #' @seealso 77 | #' [create_acr], [delete_acr] 78 | #' 79 | #' [az_container_registry] 80 | #' 81 | #' [docker_registry] for the registry endpoint 82 | #' 83 | #' [ACR documentation](https://learn.microsoft.com/en-us/azure/container-registry/) and 84 | #' [API reference](https://learn.microsoft.com/en-us/rest/api/containerregistry/registries) 85 | #' 86 | #' [Docker registry API](https://docs.docker.com/registry/spec/api/) 87 | #' 88 | #' @examples 89 | #' \dontrun{ 90 | #' 91 | #' rg <- AzureRMR::get_azure_login()$ 92 | #' get_subscription("subscription_id")$ 93 | #' get_resource_group("rgname") 94 | #' 95 | #' rg$get_acr("myregistry") 96 | #' 97 | #' } 98 | NULL 99 | 100 | 101 | #' Delete an Azure Container Registry (ACR) 102 | #' 103 | #' Method for the [AzureRMR::az_resource_group] class. 104 | #' 105 | #' @rdname delete_acr 106 | #' @name delete_acr 107 | #' @aliases delete_acr 108 | #' 109 | #' @section Usage: 110 | #' ``` 111 | #' delete_acr(name, confirm=TRUE, wait=FALSE) 112 | #' ``` 113 | #' @section Arguments: 114 | #' - `name`: The name of the container registry. 115 | #' - `confirm`: Whether to ask for confirmation before deleting. 116 | #' - `wait`: Whether to wait until the deletion is complete. 117 | #' 118 | #' @section Value: 119 | #' NULL on successful deletion. 120 | #' 121 | #' @seealso 122 | #' [create_acr], [get_acr] 123 | #' 124 | #' [az_container_registry] 125 | #' 126 | #' [docker_registry] for the registry endpoint 127 | #' 128 | #' [ACR documentation](https://learn.microsoft.com/en-us/azure/container-registry/) and 129 | #' [API reference](https://learn.microsoft.com/en-us/rest/api/containerregistry/registries) 130 | #' 131 | #' [Docker registry API](https://docs.docker.com/registry/spec/api/) 132 | #' 133 | #' @examples 134 | #' \dontrun{ 135 | #' 136 | #' rg <- AzureRMR::get_azure_login()$ 137 | #' get_subscription("subscription_id")$ 138 | #' get_resource_group("rgname") 139 | #' 140 | #' rg$delete_acr("myregistry") 141 | #' 142 | #' } 143 | NULL 144 | 145 | 146 | add_acr_methods <- function() 147 | { 148 | az_resource_group$set("public", "create_acr", overwrite=TRUE, 149 | function(name, location=self$location, 150 | admin_user_enabled=TRUE, sku="Standard", ..., wait=TRUE) 151 | { 152 | AzureContainers::acr$new(self$token, self$subscription, self$name, 153 | type="Microsoft.containerRegistry/registries", name=name, location=location, 154 | properties=list(adminUserEnabled=admin_user_enabled), 155 | sku=list(name=sku, tier=sku), 156 | ..., wait=wait) 157 | }) 158 | 159 | az_resource_group$set("public", "get_acr", overwrite=TRUE, 160 | function(name) 161 | { 162 | AzureContainers::acr$new(self$token, self$subscription, self$name, 163 | type="Microsoft.containerRegistry/registries", name=name) 164 | }) 165 | 166 | az_resource_group$set("public", "delete_acr", overwrite=TRUE, 167 | function(name, confirm=TRUE, wait=FALSE) 168 | { 169 | self$get_acr(name)$delete(confirm=confirm, wait=wait) 170 | }) 171 | 172 | az_resource_group$set("public", "list_acrs", overwrite=TRUE, 173 | function() 174 | { 175 | provider <- "Microsoft.ContainerRegistry" 176 | path <- "registries" 177 | api_version <- az_subscription$ 178 | new(self$token, self$subscription)$ 179 | get_provider_api_version(provider, path) 180 | 181 | op <- file.path("resourceGroups", self$name, "providers", provider, path) 182 | 183 | cont <- call_azure_rm(self$token, self$subscription, op, api_version=api_version) 184 | lst <- lapply(cont$value, 185 | function(parms) AzureContainers::acr$new(self$token, self$subscription, deployed_properties=parms)) 186 | 187 | # keep going until paging is complete 188 | while(!is_empty(cont$nextLink)) 189 | { 190 | cont <- call_azure_url(self$token, cont$nextLink) 191 | lst <- lapply(cont$value, 192 | function(parms) AzureContainers::acr$new(self$token, self$subscription, deployed_properties=parms)) 193 | } 194 | named_list(lst) 195 | }) 196 | 197 | az_subscription$set("public", "list_acrs", overwrite=TRUE, 198 | function() 199 | { 200 | provider <- "Microsoft.ContainerRegistry" 201 | path <- "registries" 202 | api_version <- self$get_provider_api_version(provider, path) 203 | 204 | op <- file.path("providers", provider, path) 205 | 206 | cont <- call_azure_rm(self$token, self$id, op, api_version=api_version) 207 | lst <- lapply(cont$value, 208 | function(parms) AzureContainers::acr$new(self$token, self$id, deployed_properties=parms)) 209 | 210 | # keep going until paging is complete 211 | while(!is_empty(cont$nextLink)) 212 | { 213 | cont <- call_azure_url(self$token, cont$nextLink) 214 | lst <- lapply(cont$value, 215 | function(parms) AzureContainers::acr$new(self$token, self$id, deployed_properties=parms)) 216 | } 217 | named_list(lst) 218 | }) 219 | } 220 | 221 | 222 | -------------------------------------------------------------------------------- /R/add_vmsize_methods.R: -------------------------------------------------------------------------------- 1 | #' List available VM sizes 2 | #' 3 | #' Method for the [AzureRMR::az_subscription] and [AzureRMR::az_resource_group] classes. 4 | #' 5 | #' @section Usage: 6 | #' ``` 7 | #' ## R6 method for class 'az_subscription' 8 | #' list_vm_sizes(location, name_only = FALSE) 9 | #' 10 | #' ## R6 method for class 'az_resource_group' 11 | #' list_vm_sizes(name_only = FALSE) 12 | #' ``` 13 | #' @section Arguments: 14 | #' - `location`: For the subscription class method, the location/region for which to obtain available VM sizes. 15 | #' - `name_only`: Whether to return only a vector of names, or all information on each VM size. 16 | #' 17 | #' @section Value: 18 | #' If `name_only` is TRUE, a character vector of names. If FALSE, a data frame containing the following information for each VM size: the name, number of cores, OS disk size, resource disk size, memory, and maximum data disks. 19 | #' 20 | #' @examples 21 | #' \dontrun{ 22 | #' 23 | #' sub <- AzureRMR::get_azure_login()$ 24 | #' get_subscription("subscription_id") 25 | #' 26 | #' sub$list_vm_sizes("australiaeast") 27 | #' 28 | #' # same output as above 29 | #' rg <- sub$create_resource_group("rgname", location="australiaeast") 30 | #' rg$list_vm_sizes() 31 | #' 32 | #' } 33 | #' @rdname list_vm_sizes 34 | #' @aliases list_vm_sizes 35 | #' @name list_vm_sizes 36 | NULL 37 | 38 | 39 | # extend subscription methods 40 | add_vmsize_methods <- function() 41 | { 42 | az_subscription$set("public", "list_vm_sizes", overwrite=TRUE, 43 | function(location, name_only=FALSE) 44 | { 45 | provider <- "Microsoft.Compute" 46 | path <- "locations" 47 | api_version <- self$get_provider_api_version(provider, path) 48 | 49 | op <- file.path("providers", provider, path, location, "vmSizes") 50 | res <- call_azure_rm(self$token, self$id, op, api_version=api_version) 51 | 52 | if(!name_only) 53 | do.call(rbind, lapply(res$value, data.frame, stringsAsFactors=FALSE)) 54 | else sapply(res$value, `[[`, "name") 55 | }) 56 | 57 | az_resource_group$set("public", "list_vm_sizes", overwrite=TRUE, 58 | function(name_only=FALSE) 59 | { 60 | az_subscription$ 61 | new(self$token, self$subscription)$ 62 | list_vm_sizes(self$location, name_only=name_only) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /R/az_agent_pool.R: -------------------------------------------------------------------------------- 1 | # stub class, may be expanded later 2 | az_agent_pool <- R6::R6Class("az_agent_pool", inherit=AzureRMR::az_resource) 3 | -------------------------------------------------------------------------------- /R/az_container_instance.R: -------------------------------------------------------------------------------- 1 | #' Azure Container Instance class 2 | #' 3 | #' Class representing an Azure Container Instance (ACI) resource. 4 | #' 5 | #' @docType class 6 | #' @section Methods: 7 | #' The following methods are available, in addition to those provided by the [AzureRMR::az_resource] class: 8 | #' - `new(...)`: Initialize a new ACI object. 9 | #' - `restart()`, `start()`: Start a stopped container. These methods are synonyms for each other. 10 | #' - `stop()`: Stop a container. 11 | #' 12 | #' @section Details: 13 | #' Initializing a new object of this class can either retrieve an existing ACI resource, or create a new resource on the host. Generally, the best way to initialize an object is via the `get_aci`, `create_aci` or `list_acis` methods of the [AzureRMR::az_resource_group] class, which handle the details automatically. 14 | #' 15 | #' @seealso 16 | #' [acr], [aks] 17 | #' 18 | #' [ACI documentation](https://learn.microsoft.com/en-us/azure/container-instances/) and 19 | #' [API reference](https://learn.microsoft.com/en-us/rest/api/container-instances/) 20 | #' 21 | #' [Docker commandline reference](https://docs.docker.com/engine/reference/commandline/cli/) 22 | #' 23 | #' @examples 24 | #' \dontrun{ 25 | #' 26 | #' rg <- AzureRMR::get_azure_login()$ 27 | #' get_subscription("subscription_id")$ 28 | #' get_resource_group("rgname") 29 | #' 30 | #' myaci <- rg$get_aci("mycontainer") 31 | #' 32 | #' myaci$stop() 33 | #' myaci$restart() 34 | #' 35 | #' } 36 | #' @aliases az_container_instance 37 | #' @export 38 | aci <- R6::R6Class("az_container_instance", inherit=AzureRMR::az_resource, 39 | 40 | public=list( 41 | 42 | restart=function() 43 | { 44 | private$res_op("restart", http_verb="POST") 45 | }, 46 | 47 | start=function() 48 | { 49 | private$res_op("start", http_verb="POST") 50 | }, 51 | 52 | stop=function() 53 | { 54 | private$res_op("stop", http_verb="POST") 55 | } 56 | )) 57 | 58 | 59 | #' Utilities for specifying ACI configuration information 60 | #' 61 | #' @param port,protocol For `aci_ports`, vectors of the port numbers and protocols to open for the instance. 62 | #' @param server,username,password For `aci_creds`, the authentication details for a Docker registry. See [docker_registry]. 63 | #' @param lst for `get_aci_credentials_list`, a list of objects. 64 | #' 65 | #' @details 66 | #' These are helper functions to be used in specifying the configuration for a container instance. Only `aci_ports` and `aci_creds` are meant to be called by the user; `get_aci_credentials_list` is exported to workaround namespacing issues on startup. 67 | #' 68 | #' @seealso [create_aci], [aci], [docker_registry] 69 | #' @rdname aci_utils 70 | #' @export 71 | aci_ports <- function(port=c(80L, 443L), protocol="TCP") 72 | { 73 | df <- data.frame(port=as.integer(port), protocol=protocol, stringsAsFactors=FALSE) 74 | lapply(seq_len(nrow(df)), function(i) unclass(df[i, ])) 75 | } 76 | 77 | 78 | #' @rdname aci_utils 79 | #' @export 80 | aci_creds <- function(server, username=NULL, password=NULL) 81 | { 82 | if(is.null(username)) 83 | stop("No container registry identity supplied", call.=FALSE) 84 | 85 | obj <- list(server=server, username=username, password=password) 86 | class(obj) <- "aci_creds" 87 | obj 88 | } 89 | 90 | 91 | #' @rdname aci_utils 92 | #' @export 93 | get_aci_credentials_list <- function(lst) 94 | { 95 | # try to ensure we actually have a list of registries as input 96 | if(is_acr(lst) || is_docker_registry(lst) || inherits(lst, "aci_creds") || !is.list(lst)) 97 | lst <- list(lst) 98 | lapply(lst, function(x) extract_creds(x)) 99 | } 100 | 101 | extract_creds <- function(obj, ...) 102 | { 103 | UseMethod("extract_creds") 104 | } 105 | 106 | extract_creds.az_container_registry <- function(obj, ...) 107 | { 108 | extract_creds(obj$get_docker_registry()) 109 | } 110 | 111 | extract_creds.DockerRegistry <- function(obj, ...) 112 | { 113 | if(is.null(obj$username) || is.null(obj$password)) 114 | stop("Docker registry object does not contain a username/password", call.=FALSE) 115 | 116 | list(server=obj$server$hostname, username=obj$username, password=obj$password) 117 | } 118 | 119 | extract_creds.aci_creds <- function(obj, ...) 120 | { 121 | unclass(obj) 122 | } 123 | -------------------------------------------------------------------------------- /R/az_container_registry.R: -------------------------------------------------------------------------------- 1 | #' Azure Container Registry class 2 | #' 3 | #' Class representing an Azure Container Registry (ACR) resource. For working with the registry endpoint itself, including uploading and downloading images etc, see [docker_registry]. 4 | #' 5 | #' @docType class 6 | #' @section Methods: 7 | #' The following methods are available, in addition to those provided by the [AzureRMR::az_resource] class: 8 | #' - `new(...)`: Initialize a new ACR object. See 'Details'. 9 | #' - `add_role_assignment(principal, role, scope=NULL, ...)`: Adds a role for the specified principal. This is an override mainly to handle AKS objects, so that the Kubernetes cluster can be granted access to the registry. You can use the `...` arguments to supply authentication details for AzureGraph, which is used to retrieve the cluster service principal. 10 | #' - `list_credentials`: Return the username and passwords for this registry. Only valid if the Admin user for the registry has been enabled. 11 | #' - `list_policies`: Return the policies for this registry. 12 | #' - `list_usages`: Return the usage for this registry. 13 | #' - `get_docker_registry(username, password)`: Return an object representing the Docker registry endpoint. 14 | #' 15 | #' @section Details: 16 | #' Initializing a new object of this class can either retrieve an existing registry resource, or create a new registry on the host. Generally, the best way to initialize an object is via the `get_acr`, `create_acr` or `list_acrs` methods of the [AzureRMR::az_resource_group] class, which handle the details automatically. 17 | #' 18 | #' Note that this class is separate from the Docker registry itself. This class exposes methods for working with the Azure resource: listing credentials, updating resource tags, updating and deleting the resource, and so on. 19 | #' 20 | #' For working with the registry, including uploading and downloading images, updating tags, deleting layers and images etc, use the endpoint object generated with `get_docker_registry`. This method takes two optional arguments: 21 | #' 22 | #' - `username`: The username that Docker will use to authenticate with the registry. 23 | #' - `password`: The password that Docker will use to authenticate with the registry. 24 | #' 25 | #' By default, these arguments will be retrieved from the ACR resource. They will only exist if the resource was created with `admin_user_enabled=TRUE`. Currently AzureContainers does not support authentication methods other than a username/password combination. 26 | #' 27 | #' @seealso 28 | #' [create_acr], [get_acr], [delete_acr], [list_acrs] 29 | #' 30 | #' [docker_registry] for interacting with the Docker registry endpoint 31 | #' 32 | #' [Azure Container Registry](https://learn.microsoft.com/en-us/azure/container-registry/) and 33 | #' [API reference](https://learn.microsoft.com/en-us/rest/api/containerregistry/registries) 34 | #' 35 | #' @examples 36 | #' \dontrun{ 37 | #' 38 | #' rg <- AzureRMR::get_azure_login()$ 39 | #' get_subscription("subscription_id")$ 40 | #' get_resource_group("rgname") 41 | #' 42 | #' myacr <- rg$get_acr("myregistry") 43 | #' 44 | #' myacr$list_credentials() 45 | #' myacr$list_policies() 46 | #' 47 | #' # see who has push and pull access 48 | #' myacr$list_role_assignments() 49 | #' 50 | #' # grant a Kubernetes cluster pull access 51 | #' myaks <- rg$get_aks("myaks") 52 | #' myacr$add_role_assignment(myaks, "Acrpull") 53 | #' 54 | #' # get the registry endpoint (for interactive use) 55 | #' myacr$get_docker_registry() 56 | #' 57 | #' # get the registry endpoint (admin user account) 58 | #' myacr$get_docker_registry(as_admin=TRUE) 59 | #' 60 | #' } 61 | #' @aliases az_container_registry 62 | #' @export 63 | acr <- R6::R6Class("az_container_registry", inherit=AzureRMR::az_resource, 64 | 65 | public=list( 66 | 67 | add_role_assignment=function(principal, role, scope=NULL, ...) 68 | { 69 | if(is_aks(principal)) 70 | { 71 | clientid <- principal$properties$servicePrincipalProfile$clientId 72 | if(clientid == "msi") 73 | { 74 | ident <- principal$properties$identityProfile 75 | principal <- ident[[1]]$objectId 76 | } 77 | else 78 | { 79 | tenant <- self$token$tenant 80 | principal <- graph_login(tenant, ...)$get_app(clientid) 81 | 82 | } 83 | } 84 | super$add_role_assignment(principal, role, scope) 85 | }, 86 | 87 | list_credentials=function() 88 | { 89 | if(!self$properties$adminUserEnabled) 90 | stop("Admin user account is disabled", call.=FALSE) 91 | 92 | creds <- private$res_op("listCredentials", http_verb="POST") 93 | pwds <- sapply(creds$passwords, `[[`, "value") 94 | names(pwds) <- sapply(creds$passwords, `[[`, "name") 95 | list(username=creds$username, passwords=pwds) 96 | }, 97 | 98 | list_policies=function() 99 | { 100 | private$res_op("listPolicies") 101 | }, 102 | 103 | list_usages=function() 104 | { 105 | use <- private$res_op("listUsages")$value 106 | do.call(rbind, lapply(use, as.data.frame)) 107 | }, 108 | 109 | get_docker_registry=function(..., as_admin=FALSE, token=self$token) 110 | { 111 | server <- paste0("https://", self$properties$loginServer) 112 | if(as_admin) 113 | { 114 | creds <- self$list_credentials() 115 | docker_registry(server, username=creds$username, password=creds$passwords[1], app=NULL) 116 | } 117 | else docker_registry(server, ..., token=token) 118 | } 119 | )) 120 | -------------------------------------------------------------------------------- /R/ext_tools.R: -------------------------------------------------------------------------------- 1 | #' Call the docker commandline tool 2 | #' 3 | #' @param cmd The docker command. This should be a _vector_ of individual docker arguments, but can also be a single commandline string. See below. 4 | #' @param echo Whether to echo the output of the command to the console. 5 | #' @param ... Other arguments to pass to [processx::run]. 6 | #' 7 | #' @details 8 | #' This function calls the `docker` binary, which must be located in your search path. AzureContainers will search for the binary at package startup, and print a warning if it is not found. 9 | #' 10 | #' The docker command should be specified as a vector of the individual arguments, which is what `processx::run` expects. If a single string is passed, for convenience and back-compatibility reasons `call_docker` will split it into arguments for you. This is prone to error, for example if you are working with pathnames that contain spaces, so it's strongly recommended to pass a vector of arguments as a general practice. 11 | #' 12 | #' @return 13 | #' A list with the following components: 14 | #' - `status`: The exit status of the docker tool. If this is `NA`, then the process was killed and had no exit status. 15 | #' - `stdout`: The standard output of the command, in a character scalar. 16 | #' - `stderr`: The standard error of the command, in a character scalar. 17 | #' - `timeout`: Whether the process was killed because of a timeout. 18 | #' - `cmdline`: The command line. 19 | #' 20 | #' The first four components are from `processx::run`; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 21 | #' 22 | #' @seealso 23 | #' [processx::run], [call_docker_compose], [call_kubectl] for the equivalent interface to the `kubectl` Kubernetes tool 24 | #' 25 | #' [docker_registry] 26 | #' 27 | #' [Docker command line reference](https://docs.docker.com/engine/reference/commandline/cli/) 28 | #' 29 | #' @examples 30 | #' \dontrun{ 31 | #' 32 | #' # without any args, prints the docker help screen 33 | #' call_docker() 34 | #' 35 | #' # build an image: recommended usage 36 | #' call_docker(c("build", "-t", "myimage", ".")) 37 | #' 38 | #' # alternative usage, will be split into individual arguments 39 | #' call_docker("build -t myimage .") 40 | #' 41 | #' # list running containers 42 | #' call_docker(c("container", "ls")) 43 | #' 44 | #' # prune unused containers and images 45 | #' call_docker(c("container", "prune", "-f")) 46 | #' call_docker(c("image", "prune", "-f")) 47 | #' 48 | #' } 49 | #' @export 50 | call_docker <- function(cmd="", ..., echo=getOption("azure_containers_tool_echo", TRUE)) 51 | { 52 | if(.AzureContainers$docker == "") 53 | stop("docker binary not found", call.=FALSE) 54 | 55 | if(length(cmd) == 1 && grepl(" ", cmd, fixed=TRUE)) 56 | cmd <- strsplit(cmd, "\\s+")[[1]] 57 | 58 | win <- .Platform$OS.type == "windows" 59 | if(!win) 60 | { 61 | dockercmd <- "sudo" 62 | realcmd <- c(.AzureContainers$docker, cmd) 63 | } 64 | else 65 | { 66 | dockercmd <- .AzureContainers$docker 67 | realcmd <- cmd 68 | } 69 | 70 | echo <- as.logical(echo) 71 | val <- processx::run(dockercmd, realcmd, ..., echo=echo) 72 | val$cmdline <- paste("docker", paste(realcmd, collapse=" ")) 73 | invisible(val) 74 | } 75 | 76 | 77 | #' Call the docker-compose commandline tool 78 | #' 79 | #' @param cmd The docker-compose command line to execute. This should be a _vector_ of individual docker-compose arguments, but can also be a single commandline string. See below. 80 | #' @param echo Whether to echo the output of the command to the console. 81 | #' @param ... Other arguments to pass to [processx::run]. 82 | #' 83 | #' @details 84 | #' This function calls the `docker-compose` binary, which must be located in your search path. AzureContainers will search for the binary at package startup, and print a warning if it is not found. 85 | #' 86 | #' The docker-compose command should be specified as a vector of the individual arguments, which is what `processx::run` expects. If a single string is passed, for convenience and back-compatibility reasons `call_docker_compose` will split it into arguments for you. This is prone to error, for example if you are working with pathnames that contain spaces, so it's strongly recommended to pass a vector of arguments as a general practice. 87 | #' 88 | #' @return 89 | #' A list with the following components: 90 | #' - `status`: The exit status of the docker-compose tool. If this is `NA`, then the process was killed and had no exit status. 91 | #' - `stdout`: The standard output of the command, in a character scalar. 92 | #' - `stderr`: The standard error of the command, in a character scalar. 93 | #' - `timeout`: Whether the process was killed because of a timeout. 94 | #' - `cmdline`: The command line. 95 | #' 96 | #' The first four components are from `processx::run`; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 97 | #' 98 | #' @seealso 99 | #' [processx::run], [call_docker], [call_kubectl] for the equivalent interface to the `kubectl` Kubernetes tool 100 | #' 101 | #' [docker_registry] 102 | #' 103 | #' [Docker-compose command line reference](https://docs.docker.com/compose/) 104 | #' @export 105 | call_docker_compose <- function(cmd="", ..., echo=getOption("azure_containers_tool_echo", TRUE)) 106 | { 107 | if(.AzureContainers$dockercompose == "") 108 | stop("docker-compose binary not found", call.=FALSE) 109 | 110 | if(length(cmd) == 1 && grepl(" ", cmd, fixed=TRUE)) 111 | cmd <- strsplit(cmd, "\\s+")[[1]] 112 | 113 | win <- .Platform$OS.type == "windows" 114 | if(!win) 115 | { 116 | dcmpcmd <- "sudo" 117 | realcmd <- c(.AzureContainers$dockercompose, cmd) 118 | } 119 | else 120 | { 121 | dcmpcmd <- .AzureContainers$dockercompose 122 | realcmd <- cmd 123 | } 124 | 125 | echo <- as.logical(echo) 126 | val <- processx::run(dcmpcmd, realcmd, ..., echo=echo) 127 | val$cmdline <- paste("docker-compose", paste(realcmd, collapse=" ")) 128 | invisible(val) 129 | } 130 | 131 | 132 | #' Call the Kubernetes commandline tool, kubectl 133 | #' 134 | #' @param cmd The kubectl command line to execute. This should be a _vector_ of individual kubectl arguments, but can also be a single commandline string. See below. 135 | #' @param echo Whether to echo the output of the command to the console. 136 | #' @param config The pathname of the cluster config file, if required. 137 | #' @param ... Other arguments to pass to [processx::run]. 138 | #' 139 | #' @details 140 | #' This function calls the `kubectl` binary, which must be located in your search path. AzureContainers will search for the binary at package startup, and print a warning if it is not found. 141 | #' 142 | #' The kubectl command should be specified as a vector of the individual arguments, which is what `processx::run` expects. If a single string is passed, for convenience and back-compatibility reasons `call_docker_compose` will split it into arguments for you. This is prone to error, for example if you are working with pathnames that contain spaces, so it's strongly recommended to pass a vector of arguments as a general practice. 143 | #' 144 | #' @return 145 | #' A list with the following components: 146 | #' - `status`: The exit status of the kubectl tool. If this is `NA`, then the process was killed and had no exit status. 147 | #' - `stdout`: The standard output of the command, in a character scalar. 148 | #' - `stderr`: The standard error of the command, in a character scalar. 149 | #' - `timeout`: Whether the process was killed because of a timeout. 150 | #' - `cmdline`: The command line. 151 | #' 152 | #' The first four components are from `processx::run`; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 153 | #' 154 | #' @seealso 155 | #' [processx::run], [call_docker], [call_helm] 156 | #' 157 | #' [kubernetes_cluster] 158 | #' 159 | #' [Kubectl command line reference](https://kubernetes.io/docs/reference/kubectl/overview/) 160 | #' 161 | #' @examples 162 | #' \dontrun{ 163 | #' 164 | #' # without any args, prints the kubectl help screen 165 | #' call_kubectl() 166 | #' 167 | #' # append "--help" to get help for a command 168 | #' call_kubectl(c("create", "--help")) 169 | #' 170 | #' # deploy a service from a yaml file 171 | #' call_kubectl(c("create", "-f", "deployment.yaml")) 172 | #' 173 | #' # get deployment and service status 174 | #' call_kubectl(c("get", "deployment")) 175 | #' call_kubectl(c("get", "service")) 176 | #' 177 | #' } 178 | #' @export 179 | call_kubectl <- function(cmd="", config=NULL, ..., echo=getOption("azure_containers_tool_echo", TRUE)) 180 | { 181 | if(.AzureContainers$kubectl == "") 182 | stop("kubectl binary not found", call.=FALSE) 183 | 184 | if(!is.null(config)) 185 | config <- paste0("--kubeconfig=", config) 186 | 187 | if(length(cmd) == 1 && grepl(" ", cmd, fixed=TRUE)) 188 | cmd <- strsplit(cmd, "\\s+")[[1]] 189 | 190 | echo <- as.logical(echo) 191 | val <- processx::run(.AzureContainers$kubectl, c(cmd, config), ..., echo=echo) 192 | val$cmdline <- paste("kubectl", paste(cmd, collapse=" "), config) 193 | invisible(val) 194 | } 195 | 196 | 197 | #' Call the Helm commandline tool 198 | #' 199 | #' @param cmd The Helm command line to execute. This should be a _vector_ of individual helm arguments, but can also be a single commandline string. See below. 200 | #' @param echo Whether to echo the output of the command to the console. 201 | #' @param config The pathname of the cluster config file, if required. 202 | #' @param ... Other arguments to pass to [processx::run]. 203 | #' 204 | #' @details 205 | #' This function calls the `helm` binary, which must be located in your search path. AzureContainers will search for the binary at package startup, and print a warning if it is not found. 206 | #' 207 | #' The helm command should be specified as a vector of the individual arguments, which is what `processx::run` expects. If a single string is passed, for convenience and back-compatibility reasons `call_docker_compose` will split it into arguments for you. This is prone to error, for example if you are working with pathnames that contain spaces, so it's strongly recommended to pass a vector of arguments as a general practice. 208 | #' 209 | #' @return 210 | #' A list with the following components: 211 | #' - `status`: The exit status of the helm tool. If this is `NA`, then the process was killed and had no exit status. 212 | #' - `stdout`: The standard output of the command, in a character scalar. 213 | #' - `stderr`: The standard error of the command, in a character scalar. 214 | #' - `timeout`: Whether the process was killed because of a timeout. 215 | #' - `cmdline`: The command line. 216 | #' 217 | #' The first four components are from `processx::run`; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 218 | #' 219 | #' @seealso 220 | #' [processx::run], [call_docker], [call_kubectl] 221 | #' 222 | #' [kubernetes_cluster] 223 | #' 224 | #' [Kubectl command line reference](https://kubernetes.io/docs/reference/kubectl/overview/) 225 | #' 226 | #' @export 227 | call_helm <- function(cmd="", config=NULL, ..., echo=getOption("azure_containers_tool_echo", TRUE)) 228 | { 229 | if(.AzureContainers$helm == "") 230 | stop("helm binary not found", call.=FALSE) 231 | 232 | if(!is.null(config)) 233 | config <- paste0("--kubeconfig=", config) 234 | 235 | if(length(cmd) == 1 && grepl(" ", cmd, fixed=TRUE)) 236 | cmd <- strsplit(cmd, "\\s+")[[1]] 237 | 238 | echo <- as.logical(echo) 239 | val <- processx::run(.AzureContainers$helm, c(cmd, config), ..., echo=echo) 240 | val$cmdline <- paste("helm", paste(cmd, collapse=" "), config) 241 | invisible(val) 242 | } 243 | 244 | -------------------------------------------------------------------------------- /R/graph_login.R: -------------------------------------------------------------------------------- 1 | graph_login <- function(tenant, ...) 2 | { 3 | gr <- try(AzureGraph::get_graph_login(tenant=tenant), silent=TRUE) 4 | if(inherits(gr, "try-error")) 5 | gr <- AzureGraph::create_graph_login(tenant=tenant, ...) 6 | gr 7 | } 8 | -------------------------------------------------------------------------------- /R/is.R: -------------------------------------------------------------------------------- 1 | #' Utility functions to test whether an object is of the given class. 2 | #' 3 | #' @param object An R object 4 | #' 5 | #' @details 6 | #' These functions are simple wrappers around `R6::is.R6` and `inherits`. 7 | #' 8 | #' @return 9 | #' TRUE or FALSE depending on whether the object is an R6 object of the specified class. 10 | #' @rdname is 11 | #' @export 12 | is_acr <- function(object) 13 | { 14 | R6::is.R6(object) && inherits(object, "az_container_registry") 15 | } 16 | 17 | 18 | #' @rdname is 19 | #' @export 20 | is_aks <- function(object) 21 | { 22 | R6::is.R6(object) && inherits(object, "az_kubernetes_service") 23 | } 24 | 25 | 26 | #' @rdname is 27 | #' @export 28 | is_aci <- function(object) 29 | { 30 | R6::is.R6(object) && inherits(object, "az_container_instance") 31 | } 32 | 33 | 34 | #' @rdname is 35 | #' @export 36 | is_docker_registry <- function(object) 37 | { 38 | R6::is.R6(object) && inherits(object, "DockerRegistry") 39 | } 40 | 41 | 42 | #' @rdname is 43 | #' @export 44 | is_kubernetes_cluster <- function(object) 45 | { 46 | R6::is.R6(object) && inherits(object, "KubernetesCluster") 47 | } 48 | -------------------------------------------------------------------------------- /R/kubernetes_cluster.R: -------------------------------------------------------------------------------- 1 | #' Kubernetes cluster class 2 | #' 3 | #' Class representing a [Kubernetes](https://kubernetes.io/docs/home/) cluster. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the [kubernetes_cluster] function to instantiate new objects of this class. 4 | #' 5 | #' @docType class 6 | #' @section Methods: 7 | #' The following methods are available, in addition to those provided by the [AzureRMR::az_resource] class: 8 | #' - `new(...)`: Initialize a new registry object. See 'Initialization' below. 9 | #' - `create_registry_secret(registry, secret_name, email)`: Provide authentication secret for a Docker registry. See 'Secrets' below. 10 | #' - `delete_registry_secret(secret_name)`: Delete a registry authentication secret. 11 | #' - `create(file, ...)`: Creates a deployment or service from a file, using `kubectl create -f`. 12 | #' - `get(type, ...)`: Get information about resources, using `kubectl get`. 13 | #' - `run(name, image, ...)`: Run an image using `kubectl run --image`. 14 | #' - `expose(name, type, file, ...)`: Expose a service using `kubectl expose`. If the `file` argument is provided, read service information from there. 15 | #' - `delete(type, name, file, ...)`: Delete a resource (deployment or service) using `kubectl delete`. If the `file` argument is provided, read resource information from there. 16 | #' - `apply(file, ...)`: Apply a configuration file, using `kubectl apply -f`. 17 | #' - `show_dashboard(port, ...)`: Display the cluster dashboard. By default, use local port 30000. 18 | #' - `kubectl(cmd, ...)`: Run an arbitrary `kubectl` command on this cluster. Called by the other methods above. 19 | #' - `helm(cmd, ...)`: Run a `helm` command on this cluster. 20 | #' 21 | #' @section Initialization: 22 | #' The `new()` method takes one argument: `config`, the name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default `~/.kube/config` file. 23 | #' 24 | #' @section Secrets: 25 | #' The recommended way to allow a cluster to authenticate with a Docker registry is to give its service principal the appropriate role-based access. However, you can also authenticate with a username and password. To do this, call the `create_registry_secret` method with the following arguments: 26 | #' - `registry`: An object of class either [acr] representing an Azure Container Registry service, or [DockerRegistry] representing the registry itself. 27 | #' - `secret_name`: The name to give the secret. Defaults to the name of the registry server. 28 | #' - `email`: The email address for the Docker registry. 29 | #' 30 | #' @section Kubectl and helm: 31 | #' The methods for this class call the `kubectl` and `helm` commandline tools, passing the `--config` option to specify the configuration information for the cluster. This allows all the features supported by Kubernetes to be available immediately and with a minimum of effort, although it does require that the tools be installed. The returned object from a call to `kubectl` or `helm` will contain the following components: 32 | #' - `status`: The exit status. If this is `NA`, then the process was killed and had no exit status. 33 | #' - `stdout`: The standard output of the command, in a character scalar. 34 | #' - `stderr`: The standard error of the command, in a character scalar. 35 | #' - `timeout`: Whether the process was killed because of a timeout. 36 | #' - `cmdline`: The command line. 37 | #' 38 | #' The first four components are from `processx::run`; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 39 | #' 40 | #' @seealso 41 | #' [aks], [call_kubectl], [call_helm] 42 | #' 43 | #' [Kubectl commandline reference](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands) 44 | #' 45 | #' @examples 46 | #' \dontrun{ 47 | #' 48 | #' rg <- AzureRMR::get_azure_login()$ 49 | #' get_subscription("subscription_id")$ 50 | #' get_resource_group("rgname") 51 | #' 52 | #' # get the cluster endpoint 53 | #' kubclus <- rg$get_aks("mycluster")$get_cluster() 54 | #' 55 | #' # get registry authentication secret 56 | #' kubclus$create_registry_secret(rg$get_acr("myregistry")) 57 | #' 58 | #' # deploy a service 59 | #' kubclus$create("deployment.yaml") 60 | #' 61 | #' # deploy a service from an Internet URL 62 | #' kubclus$create("https://example.com/deployment.yaml") 63 | #' 64 | #' # can also supply the deployment parameters inline 65 | #' kubclus$create(" 66 | #' apiVersion: extensions/v1beta1 67 | #' kind: Deployment 68 | #' metadata: 69 | #' name: model1 70 | #' spec: 71 | #' replicas: 1 72 | #' template: 73 | #' metadata: 74 | #' labels: 75 | #' app: model1 76 | #' spec: 77 | #' containers: 78 | #' - name: model1 79 | #' image: myregistry.azurecr.io/model1 80 | #' ports: 81 | #' - containerPort: 8000 82 | #' imagePullSecrets: 83 | #' - name: myregistry.azurecr.io 84 | #' --- 85 | #' apiVersion: v1 86 | #' kind: Service 87 | #' metadata: 88 | #' name: model1-svc 89 | #' spec: 90 | #' selector: 91 | #' app: model1 92 | #' type: LoadBalancer 93 | #' ports: 94 | #' - protocol: TCP 95 | #' port: 8000") 96 | #' 97 | #' # track status 98 | #' kubclus$get("deployment") 99 | #' kubclus$get("service") 100 | #' 101 | #' } 102 | #' @export 103 | KubernetesCluster <- R6::R6Class("KubernetesCluster", 104 | 105 | public=list( 106 | 107 | initialize=function(config=NULL) 108 | { 109 | private$config <- config 110 | }, 111 | 112 | create_registry_secret=function(registry, secret_name=registry$server$hostname, email, ...) 113 | { 114 | if(is_acr(registry)) 115 | registry <- registry$get_docker_registry(as_admin=TRUE) 116 | 117 | if(!is_docker_registry(registry)) 118 | stop("Must supply a Docker registry object", call.=FALSE) 119 | 120 | if(is.null(registry$username) || is.null(registry$password)) 121 | stop("Docker registry object does not contain a username/password", call.=FALSE) 122 | 123 | cmd <- paste0("create secret docker-registry ", secret_name, 124 | " --docker-server=", registry$server$hostname, 125 | " --docker-username=", registry$username, 126 | " --docker-password=", registry$password, 127 | " --docker-email=", email) 128 | 129 | self$kubectl(cmd, ...) 130 | }, 131 | 132 | delete_registry_secret=function(secret_name, ...) 133 | { 134 | cmd <- paste0("delete secret ", secret_name) 135 | self$kubectl(cmd, ...) 136 | }, 137 | 138 | run=function(name, image, options="", ...) 139 | { 140 | cmd <- paste0("run ", name, 141 | " --image ", image, 142 | " ", options) 143 | self$kubectl(cmd, ...) 144 | }, 145 | 146 | expose=function(name, type=c("pod", "service", "replicationcontroller", "deployment", "replicaset"), 147 | file=NULL, options="", ...) 148 | { 149 | if(is.null(file)) 150 | { 151 | type <- match.arg(type) 152 | cmd <- paste0("expose ", type, 153 | " ", name, 154 | " ", options) 155 | } 156 | else 157 | { 158 | cmd <- paste0("expose -f ", make_file(file, ".yaml"), 159 | " ", options) 160 | } 161 | self$kubectl(cmd, ...) 162 | }, 163 | 164 | create=function(file, options="", ...) 165 | { 166 | cmd <- paste0("create -f ", make_file(file, ".yaml"), 167 | " ", options) 168 | self$kubectl(cmd, ...) 169 | }, 170 | 171 | apply=function(file, options="", ...) 172 | { 173 | cmd <- paste0("apply -f ", make_file(file, ".yaml"), 174 | " ", options) 175 | self$kubectl(cmd, ...) 176 | }, 177 | 178 | delete=function(type, name, file=NULL, options="", ...) 179 | { 180 | if(is.null(file)) 181 | { 182 | cmd <- paste0("delete ", type, 183 | " ", name, 184 | " ", options) 185 | } 186 | else 187 | { 188 | cmd <- paste0("delete -f ", make_file(file, ".yaml"), 189 | " ", options) 190 | } 191 | self$kubectl(cmd, ...) 192 | }, 193 | 194 | get=function(type, options="", ...) 195 | { 196 | cmd <- paste0("get ", type, 197 | " ", options) 198 | self$kubectl(cmd, ...) 199 | }, 200 | 201 | show_dashboard=function(port=30000, options="", ...) 202 | { 203 | cmd <- paste0("proxy --port=", port, 204 | " ", options) 205 | config <- if(!is.null(private$config)) 206 | paste0("--kubeconfig=", private$config) 207 | else NULL 208 | 209 | processx::process$new(.AzureContainers$kubectl, c(strsplit(cmd, " ", fixed=TRUE)[[1]], config), ...) 210 | url <- paste0("http://localhost:", 211 | port, 212 | "/api/v1/namespaces/kube-system/services/kubernetes-dashboard/proxy/#!/overview") 213 | message("If the dashboard does not appear, enter the URL '", url, "' in your browser") 214 | browseURL(url) 215 | }, 216 | 217 | kubectl=function(cmd="", ...) 218 | { 219 | call_kubectl(cmd, config=private$config, ...) 220 | }, 221 | 222 | helm=function(cmd="", ...) 223 | { 224 | call_helm(cmd, config=private$config, ...) 225 | } 226 | ), 227 | 228 | private=list( 229 | config=NULL 230 | )) 231 | 232 | 233 | #' Create a new Kubernetes cluster object 234 | #' 235 | #' @param config The name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default `~/.kube/config` file. 236 | #' @details 237 | #' Use this function to instantiate a new object of the `KubernetesCluster` class, for interacting with a Kubernetes cluster. 238 | #' @return 239 | #' An R6 object of class `KubernetesCluster`. 240 | #' 241 | #' @seealso 242 | #' [KubernetesCluster] for methods for working with the cluster, [call_kubectl], [call_helm] 243 | #' 244 | #' [docker_registry] for the corresponding function to create a Docker registry object 245 | #' 246 | #' @examples 247 | #' \dontrun{ 248 | #' 249 | #' kubernetes_cluster() 250 | #' kubernetes_cluster("myconfig.yaml") 251 | #' 252 | #' } 253 | #' @export 254 | kubernetes_cluster <- function(config=NULL) 255 | { 256 | KubernetesCluster$new(config) 257 | } 258 | 259 | 260 | # generate a file from a character vector to be passed to kubectl 261 | make_file <- function(file, ext="") 262 | { 263 | if(length(file) == 1 && (file.exists(file) || is_url(file))) 264 | return(file) 265 | 266 | out <- tempfile(fileext=ext) 267 | writeLines(file, out) 268 | out 269 | } 270 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AzureContainers 2 | 3 | [![CRAN](https://www.r-pkg.org/badges/version/AzureContainers)](https://cran.r-project.org/package=AzureContainers) 4 | ![Downloads](https://cranlogs.r-pkg.org/badges/AzureContainers) 5 | ![R-CMD-check](https://github.com/Azure/AzureContainers/workflows/R-CMD-check/badge.svg) 6 | 7 | A package for working with [Azure Container Registry (ACR)](https://azure.microsoft.com/en-us/products/container-registry/), [Azure Kubernetes Service (AKS)](https://azure.microsoft.com/en-us/products/kubernetes-service/) and [Azure Container Instances (ACI)](https://azure.microsoft.com/en-us/products/container-instances/). Extends the Azure Resource Manager interface provided by the [AzureRMR](https://github.com/Azure/AzureRMR) package. 8 | 9 | AzureContainers lets you build and deploy containerised services in R, using Docker and Kubernetes. For full functionality, you should have [Docker](https://docs.docker.com/install/) installed, as well as the [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) and [helm](https://helm.sh/) commandline tools. Otherwise it is relatively lightweight, requiring neither Powershell nor Python. 10 | 11 | Note that AzureContainers can talk to any Docker registry that uses the [V2 HTTP API](https://docs.docker.com/registry/spec/api/), not just those created via ACR. Similarly, it can interface with Kubernetes clusters anywhere, not just those created via AKS. 12 | 13 | The primary repo for this package is at https://github.com/Azure/AzureContainers; please submit issues and PRs there. It is also mirrored at the Cloudyr org at https://github.com/cloudyr/AzureContainers. You can install the development version of the package with `devtools::install_github("Azure/AzureContainers")`. 14 | 15 | ## Example workflow 16 | 17 | Here is a sample R workflow to package up an R model as a container, deploy it to a Kubernetes cluster, and expose it as a service. 18 | 19 | ```r 20 | library(AzureContainers) 21 | 22 | az <- AzureRMR::get_azure_login() 23 | resgroup <- az$ 24 | get_subscription("")$ 25 | create_resource_group("myresgroup", location="australiaeast") 26 | 27 | # create container registry 28 | acr <- resgroup$create_acr("myacr", location="australiaeast") 29 | 30 | # create Docker image from a predefined Dockerfile 31 | call_docker("build -t newcontainer .") 32 | 33 | # get registry endpoint, upload image 34 | reg <- acr$get_docker_registry() 35 | reg$push("newcontainer") 36 | 37 | 38 | # create Kubernetes cluster with 2 nodes 39 | aks <- resgroup$create_aks("myakscluster", 40 | location="australiaeast", 41 | agent_pools=agent_pool("pool1", 2)) 42 | 43 | # give the cluster pull access to the registry 44 | acr$add_role_assignment(aks, "Acrpull") 45 | 46 | # get cluster endpoint, deploy from ACR to AKS with predefined yaml definition file 47 | clus <- aks$get_cluster() 48 | clus$create("model1.yaml") 49 | clus$get("service") 50 | ``` 51 | 52 | --- 53 |

54 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /man/DockerRegistry.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker_registry.R 3 | \docType{class} 4 | \name{DockerRegistry} 5 | \alias{DockerRegistry} 6 | \title{Docker registry class} 7 | \description{ 8 | Class representing a \href{https://docs.docker.com/registry/}{Docker registry}. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the \link{docker_registry} function to instantiate new objects of this class. 9 | } 10 | \section{Methods}{ 11 | 12 | The following methods are available, in addition to those provided by the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class: 13 | \itemize{ 14 | \item \code{login(...)}: Do a local login to the registry via \verb{docker login}; necessary if you want to push and pull images. By default, instantiating a new object of this class will also log you in. See 'Details' below. 15 | \item \code{push(src_image, dest_image, ...)}: Push an image to the registry, using \verb{docker tag} and \verb{docker push}. 16 | \item \code{pull(image, ...)}: Pull an image from the registry, using \verb{docker pull}. 17 | \item \code{get_image_manifest(image, tag="latest")}: Gets the manifest for an image. 18 | \item \code{get_image_digest(image, tag="latest")}: Gets the digest (SHA hash) for an image. 19 | \item \code{delete_image(image, digest, confirm=TRUE)}: Deletes an image from the registry. 20 | \item \code{list_repositories()}: Lists the repositories (images) in the registry. 21 | } 22 | } 23 | 24 | \section{Details}{ 25 | 26 | The arguments to the \code{login()} method are: 27 | \itemize{ 28 | \item \code{tenant}: The Azure Active Directory (AAD) tenant for the registry. 29 | \item \code{username}: The username that Docker will use to authenticate with the registry. This can be either the admin username, if the registry was created with an admin account, or the ID of a registered app that has access to the registry. 30 | \item \code{password}: The password that Docker will use to authenticate with the registry. 31 | \item \code{app}: The app ID to use to authenticate with the registry. Set this to NULL to authenticate with a username and password, rather than via AAD. 32 | \item \code{...}: Further arguments passed to \link[AzureAuth:get_azure_token]{AzureAuth::get_azure_token}. 33 | \item \code{token}: An Azure token object. If supplied, all authentication details will be inferred from this. 34 | } 35 | 36 | The \code{login()}, \code{push()} and \code{pull()} methods for this class call the \code{docker} commandline tool under the hood. This allows all the features supported by Docker to be available immediately, with a minimum of effort. Any calls to the \code{docker} tool will also contain the full commandline as the \code{cmdline} attribute of the (invisible) returned value; this allows scripts to be developed that can be run outside R. 37 | } 38 | 39 | \examples{ 40 | \dontrun{ 41 | 42 | reg <- docker_registry("myregistry") 43 | 44 | reg$list_repositories() 45 | 46 | # create an image from a Dockerfile in the current directory 47 | call_docker(c("build", "-t", "myimage", ".")) 48 | 49 | # push the image 50 | reg$push("myimage") 51 | 52 | reg$get_image_manifest("myimage") 53 | reg$get_image_digest("myimage") 54 | 55 | } 56 | } 57 | \seealso{ 58 | \link{acr}, \link{docker_registry}, \link{call_docker} 59 | 60 | \href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker commandline reference} 61 | 62 | \href{https://docs.docker.com/registry/spec/api/}{Docker registry API} 63 | } 64 | -------------------------------------------------------------------------------- /man/KubernetesCluster.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/kubernetes_cluster.R 3 | \docType{class} 4 | \name{KubernetesCluster} 5 | \alias{KubernetesCluster} 6 | \title{Kubernetes cluster class} 7 | \description{ 8 | Class representing a \href{https://kubernetes.io/docs/home/}{Kubernetes} cluster. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the \link{kubernetes_cluster} function to instantiate new objects of this class. 9 | } 10 | \section{Methods}{ 11 | 12 | The following methods are available, in addition to those provided by the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class: 13 | \itemize{ 14 | \item \code{new(...)}: Initialize a new registry object. See 'Initialization' below. 15 | \item \code{create_registry_secret(registry, secret_name, email)}: Provide authentication secret for a Docker registry. See 'Secrets' below. 16 | \item \code{delete_registry_secret(secret_name)}: Delete a registry authentication secret. 17 | \item \code{create(file, ...)}: Creates a deployment or service from a file, using \verb{kubectl create -f}. 18 | \item \code{get(type, ...)}: Get information about resources, using \verb{kubectl get}. 19 | \item \code{run(name, image, ...)}: Run an image using \verb{kubectl run --image}. 20 | \item \code{expose(name, type, file, ...)}: Expose a service using \verb{kubectl expose}. If the \code{file} argument is provided, read service information from there. 21 | \item \code{delete(type, name, file, ...)}: Delete a resource (deployment or service) using \verb{kubectl delete}. If the \code{file} argument is provided, read resource information from there. 22 | \item \code{apply(file, ...)}: Apply a configuration file, using \verb{kubectl apply -f}. 23 | \item \code{show_dashboard(port, ...)}: Display the cluster dashboard. By default, use local port 30000. 24 | \item \code{kubectl(cmd, ...)}: Run an arbitrary \code{kubectl} command on this cluster. Called by the other methods above. 25 | \item \code{helm(cmd, ...)}: Run a \code{helm} command on this cluster. 26 | } 27 | } 28 | 29 | \section{Initialization}{ 30 | 31 | The \code{new()} method takes one argument: \code{config}, the name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default \verb{~/.kube/config} file. 32 | } 33 | 34 | \section{Secrets}{ 35 | 36 | The recommended way to allow a cluster to authenticate with a Docker registry is to give its service principal the appropriate role-based access. However, you can also authenticate with a username and password. To do this, call the \code{create_registry_secret} method with the following arguments: 37 | \itemize{ 38 | \item \code{registry}: An object of class either \link{acr} representing an Azure Container Registry service, or \link{DockerRegistry} representing the registry itself. 39 | \item \code{secret_name}: The name to give the secret. Defaults to the name of the registry server. 40 | \item \code{email}: The email address for the Docker registry. 41 | } 42 | } 43 | 44 | \section{Kubectl and helm}{ 45 | 46 | The methods for this class call the \code{kubectl} and \code{helm} commandline tools, passing the \code{--config} option to specify the configuration information for the cluster. This allows all the features supported by Kubernetes to be available immediately and with a minimum of effort, although it does require that the tools be installed. The returned object from a call to \code{kubectl} or \code{helm} will contain the following components: 47 | \itemize{ 48 | \item \code{status}: The exit status. If this is \code{NA}, then the process was killed and had no exit status. 49 | \item \code{stdout}: The standard output of the command, in a character scalar. 50 | \item \code{stderr}: The standard error of the command, in a character scalar. 51 | \item \code{timeout}: Whether the process was killed because of a timeout. 52 | \item \code{cmdline}: The command line. 53 | } 54 | 55 | The first four components are from \code{processx::run}; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 56 | } 57 | 58 | \examples{ 59 | \dontrun{ 60 | 61 | rg <- AzureRMR::get_azure_login()$ 62 | get_subscription("subscription_id")$ 63 | get_resource_group("rgname") 64 | 65 | # get the cluster endpoint 66 | kubclus <- rg$get_aks("mycluster")$get_cluster() 67 | 68 | # get registry authentication secret 69 | kubclus$create_registry_secret(rg$get_acr("myregistry")) 70 | 71 | # deploy a service 72 | kubclus$create("deployment.yaml") 73 | 74 | # deploy a service from an Internet URL 75 | kubclus$create("https://example.com/deployment.yaml") 76 | 77 | # can also supply the deployment parameters inline 78 | kubclus$create(" 79 | apiVersion: extensions/v1beta1 80 | kind: Deployment 81 | metadata: 82 | name: model1 83 | spec: 84 | replicas: 1 85 | template: 86 | metadata: 87 | labels: 88 | app: model1 89 | spec: 90 | containers: 91 | - name: model1 92 | image: myregistry.azurecr.io/model1 93 | ports: 94 | - containerPort: 8000 95 | imagePullSecrets: 96 | - name: myregistry.azurecr.io 97 | --- 98 | apiVersion: v1 99 | kind: Service 100 | metadata: 101 | name: model1-svc 102 | spec: 103 | selector: 104 | app: model1 105 | type: LoadBalancer 106 | ports: 107 | - protocol: TCP 108 | port: 8000") 109 | 110 | # track status 111 | kubclus$get("deployment") 112 | kubclus$get("service") 113 | 114 | } 115 | } 116 | \seealso{ 117 | \link{aks}, \link{call_kubectl}, \link{call_helm} 118 | 119 | \href{https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands}{Kubectl commandline reference} 120 | } 121 | -------------------------------------------------------------------------------- /man/aci.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/az_container_instance.R 3 | \docType{class} 4 | \name{aci} 5 | \alias{aci} 6 | \alias{az_container_instance} 7 | \title{Azure Container Instance class} 8 | \description{ 9 | Class representing an Azure Container Instance (ACI) resource. 10 | } 11 | \section{Methods}{ 12 | 13 | The following methods are available, in addition to those provided by the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class: 14 | \itemize{ 15 | \item \code{new(...)}: Initialize a new ACI object. 16 | \item \code{restart()}, \code{start()}: Start a stopped container. These methods are synonyms for each other. 17 | \item \code{stop()}: Stop a container. 18 | } 19 | } 20 | 21 | \section{Details}{ 22 | 23 | Initializing a new object of this class can either retrieve an existing ACI resource, or create a new resource on the host. Generally, the best way to initialize an object is via the \code{get_aci}, \code{create_aci} or \code{list_acis} methods of the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class, which handle the details automatically. 24 | } 25 | 26 | \examples{ 27 | \dontrun{ 28 | 29 | rg <- AzureRMR::get_azure_login()$ 30 | get_subscription("subscription_id")$ 31 | get_resource_group("rgname") 32 | 33 | myaci <- rg$get_aci("mycontainer") 34 | 35 | myaci$stop() 36 | myaci$restart() 37 | 38 | } 39 | } 40 | \seealso{ 41 | \link{acr}, \link{aks} 42 | 43 | \href{https://learn.microsoft.com/en-us/azure/container-instances/}{ACI documentation} and 44 | \href{https://learn.microsoft.com/en-us/rest/api/container-instances/}{API reference} 45 | 46 | \href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker commandline reference} 47 | } 48 | -------------------------------------------------------------------------------- /man/aci_utils.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/az_container_instance.R 3 | \name{aci_ports} 4 | \alias{aci_ports} 5 | \alias{aci_creds} 6 | \alias{get_aci_credentials_list} 7 | \title{Utilities for specifying ACI configuration information} 8 | \usage{ 9 | aci_ports(port = c(80L, 443L), protocol = "TCP") 10 | 11 | aci_creds(server, username = NULL, password = NULL) 12 | 13 | get_aci_credentials_list(lst) 14 | } 15 | \arguments{ 16 | \item{port, protocol}{For \code{aci_ports}, vectors of the port numbers and protocols to open for the instance.} 17 | 18 | \item{server, username, password}{For \code{aci_creds}, the authentication details for a Docker registry. See \link{docker_registry}.} 19 | 20 | \item{lst}{for \code{get_aci_credentials_list}, a list of objects.} 21 | } 22 | \description{ 23 | Utilities for specifying ACI configuration information 24 | } 25 | \details{ 26 | These are helper functions to be used in specifying the configuration for a container instance. Only \code{aci_ports} and \code{aci_creds} are meant to be called by the user; \code{get_aci_credentials_list} is exported to workaround namespacing issues on startup. 27 | } 28 | \seealso{ 29 | \link{create_aci}, \link{aci}, \link{docker_registry} 30 | } 31 | -------------------------------------------------------------------------------- /man/acr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/az_container_registry.R 3 | \docType{class} 4 | \name{acr} 5 | \alias{acr} 6 | \alias{az_container_registry} 7 | \title{Azure Container Registry class} 8 | \description{ 9 | Class representing an Azure Container Registry (ACR) resource. For working with the registry endpoint itself, including uploading and downloading images etc, see \link{docker_registry}. 10 | } 11 | \section{Methods}{ 12 | 13 | The following methods are available, in addition to those provided by the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class: 14 | \itemize{ 15 | \item \code{new(...)}: Initialize a new ACR object. See 'Details'. 16 | \item \code{add_role_assignment(principal, role, scope=NULL, ...)}: Adds a role for the specified principal. This is an override mainly to handle AKS objects, so that the Kubernetes cluster can be granted access to the registry. You can use the \code{...} arguments to supply authentication details for AzureGraph, which is used to retrieve the cluster service principal. 17 | \item \code{list_credentials}: Return the username and passwords for this registry. Only valid if the Admin user for the registry has been enabled. 18 | \item \code{list_policies}: Return the policies for this registry. 19 | \item \code{list_usages}: Return the usage for this registry. 20 | \item \code{get_docker_registry(username, password)}: Return an object representing the Docker registry endpoint. 21 | } 22 | } 23 | 24 | \section{Details}{ 25 | 26 | Initializing a new object of this class can either retrieve an existing registry resource, or create a new registry on the host. Generally, the best way to initialize an object is via the \code{get_acr}, \code{create_acr} or \code{list_acrs} methods of the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class, which handle the details automatically. 27 | 28 | Note that this class is separate from the Docker registry itself. This class exposes methods for working with the Azure resource: listing credentials, updating resource tags, updating and deleting the resource, and so on. 29 | 30 | For working with the registry, including uploading and downloading images, updating tags, deleting layers and images etc, use the endpoint object generated with \code{get_docker_registry}. This method takes two optional arguments: 31 | \itemize{ 32 | \item \code{username}: The username that Docker will use to authenticate with the registry. 33 | \item \code{password}: The password that Docker will use to authenticate with the registry. 34 | } 35 | 36 | By default, these arguments will be retrieved from the ACR resource. They will only exist if the resource was created with \code{admin_user_enabled=TRUE}. Currently AzureContainers does not support authentication methods other than a username/password combination. 37 | } 38 | 39 | \examples{ 40 | \dontrun{ 41 | 42 | rg <- AzureRMR::get_azure_login()$ 43 | get_subscription("subscription_id")$ 44 | get_resource_group("rgname") 45 | 46 | myacr <- rg$get_acr("myregistry") 47 | 48 | myacr$list_credentials() 49 | myacr$list_policies() 50 | 51 | # see who has push and pull access 52 | myacr$list_role_assignments() 53 | 54 | # grant a Kubernetes cluster pull access 55 | myaks <- rg$get_aks("myaks") 56 | myacr$add_role_assignment(myaks, "Acrpull") 57 | 58 | # get the registry endpoint (for interactive use) 59 | myacr$get_docker_registry() 60 | 61 | # get the registry endpoint (admin user account) 62 | myacr$get_docker_registry(as_admin=TRUE) 63 | 64 | } 65 | } 66 | \seealso{ 67 | \link{create_acr}, \link{get_acr}, \link{delete_acr}, \link{list_acrs} 68 | 69 | \link{docker_registry} for interacting with the Docker registry endpoint 70 | 71 | \href{https://learn.microsoft.com/en-us/azure/container-registry/}{Azure Container Registry} and 72 | \href{https://learn.microsoft.com/en-us/rest/api/containerregistry/registries}{API reference} 73 | } 74 | -------------------------------------------------------------------------------- /man/agent_pool.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/az_kubernetes_service.R 3 | \name{agent_pool} 4 | \alias{agent_pool} 5 | \title{Utility function for specifying Kubernetes agent pools} 6 | \usage{ 7 | agent_pool(name, count, size = "Standard_DS2_v2", os = "Linux", 8 | disksize = 0, use_scaleset = TRUE, low_priority = FALSE, 9 | autoscale_nodes = FALSE, ...) 10 | } 11 | \arguments{ 12 | \item{name}{The name(s) of the pool(s).} 13 | 14 | \item{count}{The number of nodes per pool.} 15 | 16 | \item{size}{The VM type (size) to use for the pool. To see a list of available VM sizes, use the \link{list_vm_sizes} method for the resource group or subscription classes.} 17 | 18 | \item{os}{The operating system to use for the pool. Can be "Linux" or "Windows".} 19 | 20 | \item{disksize}{The OS disk size in gigabytes for each node in the pool. A value of 0 means to use the default disk size for the VM type.} 21 | 22 | \item{use_scaleset}{Whether to use a VM scaleset instead of individual VMs for this pool. A scaleset offers greater flexibility than individual VMs, and is the recommended method of creating an agent pool.} 23 | 24 | \item{low_priority}{If this pool uses a scaleset, whether it should be made up of spot (low-priority) VMs. A spot VM pool is cheaper, but is subject to being evicted to make room for other, higher-priority workloads. Ignored if \code{use_scaleset=FALSE}.} 25 | 26 | \item{autoscale_nodes}{The cluster autoscaling parameters for the pool. To enable autoscaling, set this to a vector of 2 numbers giving the minimum and maximum size of the agent pool. Ignored if \code{use_scaleset=FALSE}.} 27 | 28 | \item{...}{Other named arguments, to be used as parameters for the agent pool.} 29 | } 30 | \value{ 31 | An object of class \code{agent_pool}, suitable for passing to the \code{create_aks} constructor method. 32 | } 33 | \description{ 34 | Utility function for specifying Kubernetes agent pools 35 | } 36 | \details{ 37 | \code{agent_pool} is a convenience function to simplify the task of specifying the agent pool for a Kubernetes cluster. 38 | } 39 | \examples{ 40 | # pool of 5 Linux GPU-enabled VMs 41 | agent_pool("pool1", 5, size="Standard_NC6s_v3") 42 | 43 | # pool of 3 Windows Server VMs, 500GB disk size each 44 | agent_pool("pool1", 3, os="Windows", disksize=500) 45 | 46 | # enable cluster autoscaling, with a minimum of 1 and maximum of 10 nodes 47 | agent_pool("pool1", 5, autoscale_nodes=c(1, 10)) 48 | 49 | # use individual VMs rather than scaleset 50 | agent_pool("vmpool1", 3, use_scaleset=FALSE) 51 | 52 | } 53 | \seealso{ 54 | \link{create_aks}, \link{list_vm_sizes} 55 | 56 | \href{https://learn.microsoft.com/en-us/rest/api/aks/managedclusters/createorupdate#managedclusteragentpoolprofile}{Agent pool parameters on Microsoft Docs} 57 | } 58 | -------------------------------------------------------------------------------- /man/aks.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/az_kubernetes_service.R 3 | \docType{class} 4 | \name{aks} 5 | \alias{aks} 6 | \alias{az_kubernetes_service} 7 | \title{Azure Kubernetes Service class} 8 | \description{ 9 | Class representing an Azure Kubernetes Service (AKS) resource. For working with the cluster endpoint itself, including deploying images, creating services etc, see \link{kubernetes_cluster}. 10 | } 11 | \section{Methods}{ 12 | 13 | The following methods are available, in addition to those provided by the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class: 14 | \itemize{ 15 | \item \code{new(...)}: Initialize a new AKS object. 16 | \item \code{get_cluster(config, role)}: Return an object representing the Docker registry endpoint. 17 | \item \code{get_agent_pool(pollname)}: Returns an object of class \code{az_agent_pool} representing an agent pool. This class inherits from \link[AzureRMR:az_resource]{AzureRMR::az_resource}; it currently has no extra methods, but these may be added in the future. 18 | \item \code{list_agent_pools()}: Returns a list of agent pool objects. 19 | \item \code{create_agent_pool(poolname, ..., wait=FALSE)}: Creates a new agent pool. See the \link{agent_pool} function for further arguments to this method. 20 | \item \verb{delete_agent_pool(poolname, confirm=TRUE. wait=FALSE)}: Deletes an agent pool. 21 | \item \code{list_cluster_resources()}: Returns a list of all the Azure resources managed by the cluster. 22 | \item \code{update_aad_password(name=NULL, duration=NULL, ...)}: Update the password for Azure Active Directory integration, returning the new password invisibly. See 'Updating credentials' below. 23 | \item \code{update_service_password(name=NULL, duration=NULL, ...)}: Update the password for the service principal used to manage the cluster resources, returning the new password invisibly. See 'Updating credentials' below. 24 | } 25 | } 26 | 27 | \section{Details}{ 28 | 29 | Initializing a new object of this class can either retrieve an existing AKS resource, or create a new resource on the host. Generally, the best way to initialize an object is via the \code{get_aks}, \code{create_aks} or \code{list_aks} methods of the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class, which handle the details automatically. 30 | 31 | Note that this class is separate from the Kubernetes cluster itself. This class exposes methods for working with the Azure resource: updating resource tags, updating and deleting the resource (including updating the Kubernetes version), and so on. 32 | 33 | For working with the cluster, including deploying images, services, etc use the object generated with the \code{get_cluster} method. This method takes two optional arguments: 34 | \itemize{ 35 | \item \code{config}: The file in which to store the cluster configuration details. By default, this will be located in the AzureR configuration directory if it exists (see \link[AzureAuth:AzureR_dir]{AzureAuth::AzureR_dir}); otherwise, in the R temporary directory. To use the Kubernetes default \verb{~/.kube/config} file, set this argument to NULL. Any existing file in the given location will be overwritten. 36 | \item \code{role}: This can be \code{"User"} (the default) or \code{"Admin"}. 37 | } 38 | } 39 | 40 | \section{Updating credentials}{ 41 | 42 | An AKS resource can have up to three service principals associated with it. Two of these are for Azure Active Directory (AAD) integration. The third is used to manage the subsidiary resources (VMs, networks, disks, etc) used by the cluster, if it doesn't have a service identity. 43 | 44 | Any service principals used by the AKS resource will have secret passwords, which have to be refreshed as they expire. The \code{update_aad_password()} and \code{update_service_password()} methods let you refresh the passwords for the cluster's service principals. Their arguments are: 45 | \itemize{ 46 | \item \code{name}: An optional friendly name for the password. 47 | \item \code{duration}: The duration for which the new password is valid. Defaults to 2 years. 48 | \item \code{...}: Other arguments passed to \code{AzureGraph::create_graph_login}. Note that these are used to authenticate with Microsoft Graph, which does the actual work of updating the service principals, not to the cluster itself. 49 | } 50 | } 51 | 52 | \examples{ 53 | \dontrun{ 54 | 55 | rg <- AzureRMR::get_azure_login()$ 56 | get_subscription("subscription_id")$ 57 | get_resource_group("rgname") 58 | 59 | myaks <- rg$get_aks("mycluster") 60 | 61 | # sync with Azure: AKS resource creation can take a long time, use this to track status 62 | myaks$sync_fields() 63 | 64 | # get the cluster endpoint 65 | kubclus <- myaks$get_cluster() 66 | 67 | # list of agent pools 68 | myaks$list_agent_pools() 69 | 70 | # create a new agent pool, then delete it 71 | pool <- myaks$create_agent_pool("pool2", 3, size="Standard_DS3_v2") 72 | pool$delete() 73 | 74 | # refresh the service principal password (mostly for legacy clusters without a managed identity) 75 | myaks$update_service_password() 76 | 77 | # refresh the service principal password, using custom credentials to authenticate with MS Graph 78 | # arguments here are for Graph, not AKS! 79 | myaks$update_service_password(app="app_id", password="app_password") 80 | 81 | } 82 | } 83 | \seealso{ 84 | \link{create_aks}, \link{get_aks}, \link{delete_aks}, \link{list_aks}, \link[AzureAuth:AzureR_dir]{AzureAuth::AzureR_dir}, \link[AzureGraph:graph_login]{AzureGraph::create_graph_login} 85 | 86 | \link{kubernetes_cluster} for interacting with the cluster endpoint 87 | 88 | \href{https://learn.microsoft.com/en-us/azure/aks/}{AKS documentation} and 89 | \href{https://learn.microsoft.com/en-us/rest/api/aks/}{API reference} 90 | } 91 | -------------------------------------------------------------------------------- /man/aks_pools.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/az_kubernetes_service.R 3 | \name{aks_pools} 4 | \alias{aks_pools} 5 | \title{Vectorised utility function for specifying Kubernetes agent pools} 6 | \usage{ 7 | aks_pools(name, count, size = "Standard_DS2_v2", os = "Linux") 8 | } 9 | \arguments{ 10 | \item{name}{The name(s) of the pool(s).} 11 | 12 | \item{count}{The number of nodes per pool.} 13 | 14 | \item{size}{The VM type (size) to use for the pool. To see a list of available VM sizes, use the \link{list_vm_sizes} method for the resource group or subscription classes.} 15 | 16 | \item{os}{The operating system to use for the pool. Can be "Linux" or "Windows".} 17 | } 18 | \value{ 19 | A list of lists, suitable for passing to the \code{create_aks} constructor method. 20 | } 21 | \description{ 22 | Vectorised utility function for specifying Kubernetes agent pools 23 | } 24 | \details{ 25 | This is a convenience function to simplify the task of specifying the agent pool for a Kubernetes cluster. You can specify multiple pools by providing vectors as input arguments; any scalar inputs will be replicated to match. 26 | 27 | \code{aks_pools} is deprecated; please use \link{agent_pool} going forward. 28 | } 29 | \examples{ 30 | # 1 pool of 5 Linux VMs 31 | aks_pools("pool1", 5) 32 | 33 | # 1 pool of 3 Windows Server VMs 34 | aks_pools("pool1", 3, os="Windows") 35 | 36 | # 2 pools with different VM sizes per pool 37 | aks_pools(c("pool1", "pool2"), count=c(3, 3), size=c("Standard_DS2_v2", "Standard_DS3_v2")) 38 | 39 | } 40 | \seealso{ 41 | \link{list_vm_sizes}, \link{agent_pool} 42 | } 43 | -------------------------------------------------------------------------------- /man/call_docker.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ext_tools.R 3 | \name{call_docker} 4 | \alias{call_docker} 5 | \title{Call the docker commandline tool} 6 | \usage{ 7 | call_docker(cmd = "", ..., echo = getOption("azure_containers_tool_echo", 8 | TRUE)) 9 | } 10 | \arguments{ 11 | \item{cmd}{The docker command. This should be a \emph{vector} of individual docker arguments, but can also be a single commandline string. See below.} 12 | 13 | \item{...}{Other arguments to pass to \link[processx:run]{processx::run}.} 14 | 15 | \item{echo}{Whether to echo the output of the command to the console.} 16 | } 17 | \value{ 18 | A list with the following components: 19 | \itemize{ 20 | \item \code{status}: The exit status of the docker tool. If this is \code{NA}, then the process was killed and had no exit status. 21 | \item \code{stdout}: The standard output of the command, in a character scalar. 22 | \item \code{stderr}: The standard error of the command, in a character scalar. 23 | \item \code{timeout}: Whether the process was killed because of a timeout. 24 | \item \code{cmdline}: The command line. 25 | } 26 | 27 | The first four components are from \code{processx::run}; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 28 | } 29 | \description{ 30 | Call the docker commandline tool 31 | } 32 | \details{ 33 | This function calls the \code{docker} binary, which must be located in your search path. AzureContainers will search for the binary at package startup, and print a warning if it is not found. 34 | 35 | The docker command should be specified as a vector of the individual arguments, which is what \code{processx::run} expects. If a single string is passed, for convenience and back-compatibility reasons \code{call_docker} will split it into arguments for you. This is prone to error, for example if you are working with pathnames that contain spaces, so it's strongly recommended to pass a vector of arguments as a general practice. 36 | } 37 | \examples{ 38 | \dontrun{ 39 | 40 | # without any args, prints the docker help screen 41 | call_docker() 42 | 43 | # build an image: recommended usage 44 | call_docker(c("build", "-t", "myimage", ".")) 45 | 46 | # alternative usage, will be split into individual arguments 47 | call_docker("build -t myimage .") 48 | 49 | # list running containers 50 | call_docker(c("container", "ls")) 51 | 52 | # prune unused containers and images 53 | call_docker(c("container", "prune", "-f")) 54 | call_docker(c("image", "prune", "-f")) 55 | 56 | } 57 | } 58 | \seealso{ 59 | \link[processx:run]{processx::run}, \link{call_docker_compose}, \link{call_kubectl} for the equivalent interface to the \code{kubectl} Kubernetes tool 60 | 61 | \link{docker_registry} 62 | 63 | \href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker command line reference} 64 | } 65 | -------------------------------------------------------------------------------- /man/call_docker_compose.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ext_tools.R 3 | \name{call_docker_compose} 4 | \alias{call_docker_compose} 5 | \title{Call the docker-compose commandline tool} 6 | \usage{ 7 | call_docker_compose(cmd = "", ..., 8 | echo = getOption("azure_containers_tool_echo", TRUE)) 9 | } 10 | \arguments{ 11 | \item{cmd}{The docker-compose command line to execute. This should be a \emph{vector} of individual docker-compose arguments, but can also be a single commandline string. See below.} 12 | 13 | \item{...}{Other arguments to pass to \link[processx:run]{processx::run}.} 14 | 15 | \item{echo}{Whether to echo the output of the command to the console.} 16 | } 17 | \value{ 18 | A list with the following components: 19 | \itemize{ 20 | \item \code{status}: The exit status of the docker-compose tool. If this is \code{NA}, then the process was killed and had no exit status. 21 | \item \code{stdout}: The standard output of the command, in a character scalar. 22 | \item \code{stderr}: The standard error of the command, in a character scalar. 23 | \item \code{timeout}: Whether the process was killed because of a timeout. 24 | \item \code{cmdline}: The command line. 25 | } 26 | 27 | The first four components are from \code{processx::run}; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 28 | } 29 | \description{ 30 | Call the docker-compose commandline tool 31 | } 32 | \details{ 33 | This function calls the \code{docker-compose} binary, which must be located in your search path. AzureContainers will search for the binary at package startup, and print a warning if it is not found. 34 | 35 | The docker-compose command should be specified as a vector of the individual arguments, which is what \code{processx::run} expects. If a single string is passed, for convenience and back-compatibility reasons \code{call_docker_compose} will split it into arguments for you. This is prone to error, for example if you are working with pathnames that contain spaces, so it's strongly recommended to pass a vector of arguments as a general practice. 36 | } 37 | \seealso{ 38 | \link[processx:run]{processx::run}, \link{call_docker}, \link{call_kubectl} for the equivalent interface to the \code{kubectl} Kubernetes tool 39 | 40 | \link{docker_registry} 41 | 42 | \href{https://docs.docker.com/compose/}{Docker-compose command line reference} 43 | } 44 | -------------------------------------------------------------------------------- /man/call_helm.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ext_tools.R 3 | \name{call_helm} 4 | \alias{call_helm} 5 | \title{Call the Helm commandline tool} 6 | \usage{ 7 | call_helm(cmd = "", config = NULL, ..., 8 | echo = getOption("azure_containers_tool_echo", TRUE)) 9 | } 10 | \arguments{ 11 | \item{cmd}{The Helm command line to execute. This should be a \emph{vector} of individual helm arguments, but can also be a single commandline string. See below.} 12 | 13 | \item{config}{The pathname of the cluster config file, if required.} 14 | 15 | \item{...}{Other arguments to pass to \link[processx:run]{processx::run}.} 16 | 17 | \item{echo}{Whether to echo the output of the command to the console.} 18 | } 19 | \value{ 20 | A list with the following components: 21 | \itemize{ 22 | \item \code{status}: The exit status of the helm tool. If this is \code{NA}, then the process was killed and had no exit status. 23 | \item \code{stdout}: The standard output of the command, in a character scalar. 24 | \item \code{stderr}: The standard error of the command, in a character scalar. 25 | \item \code{timeout}: Whether the process was killed because of a timeout. 26 | \item \code{cmdline}: The command line. 27 | } 28 | 29 | The first four components are from \code{processx::run}; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 30 | } 31 | \description{ 32 | Call the Helm commandline tool 33 | } 34 | \details{ 35 | This function calls the \code{helm} binary, which must be located in your search path. AzureContainers will search for the binary at package startup, and print a warning if it is not found. 36 | 37 | The helm command should be specified as a vector of the individual arguments, which is what \code{processx::run} expects. If a single string is passed, for convenience and back-compatibility reasons \code{call_docker_compose} will split it into arguments for you. This is prone to error, for example if you are working with pathnames that contain spaces, so it's strongly recommended to pass a vector of arguments as a general practice. 38 | } 39 | \seealso{ 40 | \link[processx:run]{processx::run}, \link{call_docker}, \link{call_kubectl} 41 | 42 | \link{kubernetes_cluster} 43 | 44 | \href{https://kubernetes.io/docs/reference/kubectl/overview/}{Kubectl command line reference} 45 | } 46 | -------------------------------------------------------------------------------- /man/call_kubectl.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ext_tools.R 3 | \name{call_kubectl} 4 | \alias{call_kubectl} 5 | \title{Call the Kubernetes commandline tool, kubectl} 6 | \usage{ 7 | call_kubectl(cmd = "", config = NULL, ..., 8 | echo = getOption("azure_containers_tool_echo", TRUE)) 9 | } 10 | \arguments{ 11 | \item{cmd}{The kubectl command line to execute. This should be a \emph{vector} of individual kubectl arguments, but can also be a single commandline string. See below.} 12 | 13 | \item{config}{The pathname of the cluster config file, if required.} 14 | 15 | \item{...}{Other arguments to pass to \link[processx:run]{processx::run}.} 16 | 17 | \item{echo}{Whether to echo the output of the command to the console.} 18 | } 19 | \value{ 20 | A list with the following components: 21 | \itemize{ 22 | \item \code{status}: The exit status of the kubectl tool. If this is \code{NA}, then the process was killed and had no exit status. 23 | \item \code{stdout}: The standard output of the command, in a character scalar. 24 | \item \code{stderr}: The standard error of the command, in a character scalar. 25 | \item \code{timeout}: Whether the process was killed because of a timeout. 26 | \item \code{cmdline}: The command line. 27 | } 28 | 29 | The first four components are from \code{processx::run}; AzureContainers adds the last to make it easier to construct scripts that can be run outside R. 30 | } 31 | \description{ 32 | Call the Kubernetes commandline tool, kubectl 33 | } 34 | \details{ 35 | This function calls the \code{kubectl} binary, which must be located in your search path. AzureContainers will search for the binary at package startup, and print a warning if it is not found. 36 | 37 | The kubectl command should be specified as a vector of the individual arguments, which is what \code{processx::run} expects. If a single string is passed, for convenience and back-compatibility reasons \code{call_docker_compose} will split it into arguments for you. This is prone to error, for example if you are working with pathnames that contain spaces, so it's strongly recommended to pass a vector of arguments as a general practice. 38 | } 39 | \examples{ 40 | \dontrun{ 41 | 42 | # without any args, prints the kubectl help screen 43 | call_kubectl() 44 | 45 | # append "--help" to get help for a command 46 | call_kubectl(c("create", "--help")) 47 | 48 | # deploy a service from a yaml file 49 | call_kubectl(c("create", "-f", "deployment.yaml")) 50 | 51 | # get deployment and service status 52 | call_kubectl(c("get", "deployment")) 53 | call_kubectl(c("get", "service")) 54 | 55 | } 56 | } 57 | \seealso{ 58 | \link[processx:run]{processx::run}, \link{call_docker}, \link{call_helm} 59 | 60 | \link{kubernetes_cluster} 61 | 62 | \href{https://kubernetes.io/docs/reference/kubectl/overview/}{Kubectl command line reference} 63 | } 64 | -------------------------------------------------------------------------------- /man/create_aci.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_aci_methods.R 3 | \name{create_aci} 4 | \alias{create_aci} 5 | \title{Create Azure Container Instance (ACI)} 6 | \description{ 7 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 8 | } 9 | \section{Usage}{ 10 | 11 | 12 | \if{html}{\out{
}}\preformatted{create_aci(name, location = self$location, 13 | container = name, image, 14 | registry_creds = list(), 15 | cores = 1, memory = 8, 16 | os = c("Linux", "Windows"), 17 | command = list(), env_vars = list(), 18 | ports = aci_ports(), dns_name = name, public_ip = TRUE, 19 | restart = c("Always", "OnFailure", "Never"), managed_identity = TRUE, 20 | ...) 21 | }\if{html}{\out{
}} 22 | } 23 | 24 | \section{Arguments}{ 25 | 26 | \itemize{ 27 | \item \code{name}: The name of the ACI service. 28 | \item \code{location}: The location/region in which to create the ACI service. Defaults to this resource group's location. 29 | \item \code{container}: The name of the running container. 30 | \item \code{image}: The name of the image to run. 31 | \item \code{registry_creds}: Docker registry authentication credentials, if the image is stored in a private registry. See 'Details'. 32 | \item \code{cores}: The number of CPU cores for the instance. 33 | \item \code{memory}: The memory size in GB for the instance. 34 | \item \code{os}: The operating system to run in the instance. 35 | \item \code{command}: A list of commands to run in the instance. This is similar to the \code{--entrypoint} commandline argument to \verb{docker run}; see \href{https://learn.microsoft.com/en-us/azure/container-instances/container-instances-start-command}{here} for some examples. 36 | \item \code{env_vars}: A list of name-value pairs to set as environment variables in the instance. 37 | \item \code{secure_env_vars}: A list of name-value pairs to set as \emph{secure} environment variables in the instance. The values of these variables are not visible in the container's properties, eg when viewed in the Azure portal or via the CLI. 38 | \item \code{ports}: The network ports to open. By default, opens ports 80 and 443. See 'Details'. 39 | \item \code{dns_name}: The domain name prefix for the instance. Only takes effect if \code{public_ip=TRUE}. 40 | \item \code{public_ip}: Whether the instance should be publicly accessible. 41 | \item \code{restart}: Whether to restart the instance should an event occur. 42 | \item \code{managed_identity}: Whether to assign the container instance a managed identity. 43 | \item \code{...}: Other named arguments to pass to the \link[AzureRMR:az_resource]{AzureRMR::az_resource} initialization function. 44 | } 45 | } 46 | 47 | \section{Details}{ 48 | 49 | An ACI resource is a running container hosted in Azure. See the \href{https://learn.microsoft.com/en-us/azure/container-instances/}{documentation for the resource} for more information. Currently ACI only supports a single image in an instance. 50 | 51 | To supply the registry authentication credentials, the \code{registry_creds} argument should contain either an \link[=acr]{ACR} object, a \link{docker_registry} object, or the result of a call to the \link{aci_creds} function. 52 | 53 | The ports to open should be obtained by calling the \link{aci_ports} function. This takes a vector of port numbers as well as the protocol (TCP or UDP) for each port. 54 | } 55 | 56 | \section{Value}{ 57 | 58 | An object of class \code{az_container_instance} representing the instance. 59 | } 60 | 61 | \examples{ 62 | \dontrun{ 63 | 64 | rg <- AzureRMR::get_azure_login()$ 65 | get_subscription("subscription_id")$ 66 | get_resource_group("rgname") 67 | 68 | # get the ACR resource that contains the image 69 | myacr <- rg$get_acr("myregistry", as_admin=TRUE) 70 | 71 | rg$create_aci("mycontainer", 72 | image="myregistry.azurecr.io/myimage:latest", 73 | registry_creds=myacr) 74 | 75 | } 76 | } 77 | \seealso{ 78 | \link{get_aci}, \link{delete_aci}, \link{list_acis} 79 | 80 | \link{az_container_instance} 81 | 82 | \href{https://learn.microsoft.com/en-us/azure/container-instances/}{ACI documentation} and 83 | \href{https://learn.microsoft.com/en-us/rest/api/container-instances/}{API reference} 84 | 85 | \href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker commandline reference} 86 | } 87 | -------------------------------------------------------------------------------- /man/create_acr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_acr_methods.R 3 | \name{create_acr} 4 | \alias{create_acr} 5 | \title{Create Azure Container Registry (ACR)} 6 | \description{ 7 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 8 | } 9 | \section{Usage}{ 10 | 11 | 12 | \if{html}{\out{
}}\preformatted{create_acr(name, location = self$location, 13 | admin_user_enabled = TRUE, sku = "Standard", ...) 14 | }\if{html}{\out{
}} 15 | } 16 | 17 | \section{Arguments}{ 18 | 19 | \itemize{ 20 | \item \code{name}: The name of the container registry. 21 | \item \code{location}: The location/region in which to create the container registry. Defaults to this resource group's location. 22 | \item \code{admin_user_enabled}: Whether to enable the Admin user. Currently this must be \code{TRUE} for ACI to pull from the registry. 23 | \item \code{sku}: Either "Basic", "Standard" (the default) or "Premium". 24 | \item \code{wait}: Whether to wait until the ACR resource provisioning is complete. 25 | \item \code{...}: Other named arguments to pass to the \link[AzureRMR:az_resource]{AzureRMR::az_resource} initialization function. 26 | } 27 | } 28 | 29 | \section{Details}{ 30 | 31 | An ACR resource is a Docker registry hosted in Azure. See the \href{https://learn.microsoft.com/en-us/azure/container-registry/}{documentation for the resource} for more information. To work with the registry (transfer images, retag images, etc) see the \link[=docker_registry]{documentation for the registry endpoint}. 32 | } 33 | 34 | \section{Value}{ 35 | 36 | An object of class \code{az_container_registry} representing the registry resource. 37 | } 38 | 39 | \examples{ 40 | \dontrun{ 41 | 42 | rg <- AzureRMR::get_azure_login()$ 43 | get_subscription("subscription_id")$ 44 | get_resource_group("rgname") 45 | 46 | rg$create_acr("myregistry") 47 | 48 | } 49 | } 50 | \seealso{ 51 | \link{get_acr}, \link{delete_acr}, \link{list_acrs} 52 | 53 | \link{az_container_registry} 54 | 55 | \link{docker_registry} for the registry endpoint 56 | 57 | \href{https://learn.microsoft.com/en-us/azure/container-registry/}{ACR documentation} and 58 | \href{https://learn.microsoft.com/en-us/rest/api/containerregistry/registries}{API reference} 59 | 60 | \href{https://docs.docker.com/registry/spec/api/}{Docker registry API} 61 | } 62 | -------------------------------------------------------------------------------- /man/create_aks.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_aks_methods.R 3 | \name{create_aks} 4 | \alias{create_aks} 5 | \title{Create Azure Kubernetes Service (AKS)} 6 | \description{ 7 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 8 | } 9 | \section{Usage}{ 10 | 11 | 12 | \if{html}{\out{
}}\preformatted{create_aks(name, location = self$location, 13 | dns_prefix = name, kubernetes_version = NULL, 14 | enable_rbac = FALSE, agent_pools = agent_pool("pool1", 3), 15 | login_user = "", login_passkey = "", 16 | cluster_service_principal = NULL, managed_identity = TRUE, 17 | private_cluster = FALSE, 18 | properties = list(), ..., wait = TRUE) 19 | }\if{html}{\out{
}} 20 | } 21 | 22 | \section{Arguments}{ 23 | 24 | \itemize{ 25 | \item \code{name}: The name of the Kubernetes service. 26 | \item \code{location}: The location/region in which to create the service. Defaults to this resource group's location. 27 | \item \code{dns_prefix}: The domain name prefix to use for the cluster endpoint. The actual domain name will start with this argument, followed by a string of pseudorandom characters. 28 | \item \code{kubernetes_version}: The Kubernetes version to use. If not specified, uses the most recent version of Kubernetes available. 29 | \item \code{enable_rbac}: Whether to enable Kubernetes role-based access controls (which is distinct from Azure AD RBAC). 30 | \item \code{agent_pools}: The pool specification(s) for the cluster. See 'Details'. 31 | \item \verb{login_user,login_passkey}: Optionally, a login username and public key (on Linux). Specify these if you want to be able to ssh into the cluster nodes. 32 | \item \code{cluster_service_principal}: The service principal that AKS will use to manage the cluster resources. This should be a list, with the first component being the client ID and the second the client secret. If not supplied, a new service principal will be created (requires an interactive session). Ignored if \code{managed_identity=TRUE}, which is the default. 33 | \item \code{managed_identity}: Whether the cluster should have a managed identity assigned to it. If \code{FALSE}, a service principal will be used to manage the cluster's resources; see 'Details' below. 34 | \item \code{private_cluster}: Whether this cluster is private (not visible from the public Internet). A private cluster is accessible only to hosts on its virtual network. 35 | \item \code{properties}: A named list of further Kubernetes-specific properties to pass to the initialization function. 36 | \item \code{wait}: Whether to wait until the AKS resource provisioning is complete. Note that provisioning a Kubernetes cluster can take several minutes. 37 | \item \code{...}: Other named arguments to pass to the initialization function. 38 | } 39 | } 40 | 41 | \section{Details}{ 42 | 43 | An AKS resource is a Kubernetes cluster hosted in Azure. See the \link[=aks]{documentation for the resource} for more information. To work with the cluster (deploy images, define and start services, etc) see the \link[=kubernetes_cluster]{documentation for the cluster endpoint}. 44 | 45 | The nodes for an AKS cluster are organised into \emph{agent pools}, also known as \emph{node pools}, which are homogenous groups of virtual machines. To specify the details for a single agent pool, use the \code{agent_pool} function, which returns an S3 object of that class. To specify the details for multiple pools, you can supply a list of such objects, or a single call to the \code{aks_pools} function; see the examples below. Note that \code{aks_pools} is older, and does not support all the possible parameters for an agent pool. 46 | 47 | Of the agent pools in a cluster, at least one must be a \emph{system pool}, which is used to host critical system pods such as CoreDNS and tunnelfront. If you specify more than one pool, the first pool will be treated as the system pool. Note that there are certain \href{https://learn.microsoft.com/en-us/azure/aks/use-system-pools}{extra requirements} for the system pool. 48 | 49 | An AKS cluster requires an identity to manage the low-level resources it uses, such as virtual machines and networks. The default and recommended method is to use a \emph{managed identity}, in which all the details of this process are handled by AKS. In AzureContainers version 1.2.1 and older, a \emph{service principal} was used instead, which is an older and less automated method. By setting \code{managed_identity=FALSE}, you can continue using a service principal instead of a managed identity. 50 | 51 | One thing to be aware of with service principals is that they have a secret password that will expire eventually. By default, the password for a newly-created service principal will expire after one year. You should run the \code{update_service_password} method of the AKS object to reset/update the password before it expires. 52 | } 53 | 54 | \section{Value}{ 55 | 56 | An object of class \code{az_kubernetes_service} representing the service. 57 | } 58 | 59 | \examples{ 60 | \dontrun{ 61 | 62 | rg <- AzureRMR::get_azure_login()$ 63 | get_subscription("subscription_id")$ 64 | get_resource_group("rgname") 65 | 66 | rg$create_aks("mycluster", agent_pools=agent_pool("pool1", 5)) 67 | 68 | # GPU-enabled cluster 69 | rg$create_aks("mygpucluster", agent_pools=agent_pool("pool1", 5, size="Standard_NC6s_v3")) 70 | 71 | # multiple agent pools 72 | rg$create_aks("mycluster", agent_pools=list( 73 | agent_pool("pool1", 2), 74 | agent_pool("pool2", 3, size="Standard_NC6s_v3") 75 | )) 76 | 77 | # deprecated alternative for multiple pools 78 | rg$create_aks("mycluster", 79 | agent_pools=aks_pools(c("pool1", "pool2"), c(2, 3), c("Standard_DS2_v2", "Standard_NC6s_v3"))) 80 | 81 | } 82 | } 83 | \seealso{ 84 | \link{get_aks}, \link{delete_aks}, \link{list_aks}, \link{agent_pool}, \link{aks_pools} 85 | 86 | \link{az_kubernetes_service} 87 | 88 | \link{kubernetes_cluster} for the cluster endpoint 89 | 90 | \href{https://learn.microsoft.com/en-us/azure/aks/}{AKS documentation} and 91 | \href{https://learn.microsoft.com/en-us/rest/api/aks/}{API reference} 92 | 93 | \href{https://kubernetes.io/docs/reference/}{Kubernetes reference} 94 | } 95 | -------------------------------------------------------------------------------- /man/delete_aci.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_aci_methods.R 3 | \name{delete_aci} 4 | \alias{delete_aci} 5 | \title{Delete an Azure Container Instance (ACI)} 6 | \description{ 7 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 8 | } 9 | \section{Usage}{ 10 | 11 | 12 | \if{html}{\out{
}}\preformatted{delete_aci(name, confirm=TRUE, wait=FALSE) 13 | }\if{html}{\out{
}} 14 | } 15 | 16 | \section{Arguments}{ 17 | 18 | \itemize{ 19 | \item \code{name}: The name of the container instance. 20 | \item \code{confirm}: Whether to ask for confirmation before deleting. 21 | \item \code{wait}: Whether to wait until the deletion is complete. 22 | } 23 | } 24 | 25 | \section{Value}{ 26 | 27 | NULL on successful deletion. 28 | } 29 | 30 | \examples{ 31 | \dontrun{ 32 | 33 | rg <- AzureRMR::get_azure_login()$ 34 | get_subscription("subscription_id")$ 35 | get_resource_group("rgname") 36 | 37 | rg$delete_aci("mycontainer") 38 | 39 | } 40 | } 41 | \seealso{ 42 | \link{create_aci}, \link{get_aci} 43 | 44 | \link{az_container_instance} 45 | 46 | \href{https://learn.microsoft.com/en-us/azure/container-instances/}{ACI documentation} and 47 | \href{https://learn.microsoft.com/en-us/rest/api/container-instances/}{API reference} 48 | 49 | \href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker commandline reference} 50 | } 51 | -------------------------------------------------------------------------------- /man/delete_acr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_acr_methods.R 3 | \name{delete_acr} 4 | \alias{delete_acr} 5 | \title{Delete an Azure Container Registry (ACR)} 6 | \description{ 7 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 8 | } 9 | \section{Usage}{ 10 | 11 | 12 | \if{html}{\out{
}}\preformatted{delete_acr(name, confirm=TRUE, wait=FALSE) 13 | }\if{html}{\out{
}} 14 | } 15 | 16 | \section{Arguments}{ 17 | 18 | \itemize{ 19 | \item \code{name}: The name of the container registry. 20 | \item \code{confirm}: Whether to ask for confirmation before deleting. 21 | \item \code{wait}: Whether to wait until the deletion is complete. 22 | } 23 | } 24 | 25 | \section{Value}{ 26 | 27 | NULL on successful deletion. 28 | } 29 | 30 | \examples{ 31 | \dontrun{ 32 | 33 | rg <- AzureRMR::get_azure_login()$ 34 | get_subscription("subscription_id")$ 35 | get_resource_group("rgname") 36 | 37 | rg$delete_acr("myregistry") 38 | 39 | } 40 | } 41 | \seealso{ 42 | \link{create_acr}, \link{get_acr} 43 | 44 | \link{az_container_registry} 45 | 46 | \link{docker_registry} for the registry endpoint 47 | 48 | \href{https://learn.microsoft.com/en-us/azure/container-registry/}{ACR documentation} and 49 | \href{https://learn.microsoft.com/en-us/rest/api/containerregistry/registries}{API reference} 50 | 51 | \href{https://docs.docker.com/registry/spec/api/}{Docker registry API} 52 | } 53 | -------------------------------------------------------------------------------- /man/delete_aks.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_aks_methods.R 3 | \name{delete_aks} 4 | \alias{delete_aks} 5 | \title{Delete an Azure Kubernetes Service (AKS)} 6 | \description{ 7 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 8 | } 9 | \section{Usage}{ 10 | 11 | 12 | \if{html}{\out{
}}\preformatted{delete_aks(name, confirm=TRUE, wait=FALSE) 13 | }\if{html}{\out{
}} 14 | } 15 | 16 | \section{Arguments}{ 17 | 18 | \itemize{ 19 | \item \code{name}: The name of the Kubernetes service. 20 | \item \code{confirm}: Whether to ask for confirmation before deleting. 21 | \item \code{wait}: Whether to wait until the deletion is complete. 22 | } 23 | } 24 | 25 | \section{Value}{ 26 | 27 | NULL on successful deletion. 28 | } 29 | 30 | \examples{ 31 | \dontrun{ 32 | 33 | rg <- AzureRMR::get_azure_login()$ 34 | get_subscription("subscription_id")$ 35 | get_resource_group("rgname") 36 | 37 | rg$delete_aks("mycluster") 38 | 39 | } 40 | } 41 | \seealso{ 42 | \link{create_aks}, \link{get_aks} 43 | 44 | \link{az_kubernetes_service} 45 | 46 | \link{kubernetes_cluster} for the cluster endpoint 47 | 48 | \href{https://learn.microsoft.com/en-us/azure/aks/}{AKS documentation} and 49 | \href{https://learn.microsoft.com/en-us/rest/api/aks/}{API reference} 50 | 51 | \href{https://kubernetes.io/docs/reference/}{Kubernetes reference} 52 | } 53 | -------------------------------------------------------------------------------- /man/docker_registry.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker_registry.R 3 | \name{docker_registry} 4 | \alias{docker_registry} 5 | \title{Create a new Docker registry object} 6 | \usage{ 7 | docker_registry(server, tenant = "common", username = NULL, 8 | password = NULL, app = .az_cli_app_id, ..., domain = "azurecr.io", 9 | token = NULL, login = TRUE) 10 | } 11 | \arguments{ 12 | \item{server}{The registry server. This can be a URL ("https://myregistry.azurecr.io") or a domain name label ("myregistry"); if the latter, the value of the \code{domain} argument is appended to obtain the full hostname.} 13 | 14 | \item{tenant, username, password, app, ...}{Authentication arguments to \link[AzureAuth:get_azure_token]{AzureAuth::get_azure_token}. See 'Details' below.} 15 | 16 | \item{domain}{The default domain for the registry server.} 17 | 18 | \item{token}{An OAuth token, of class \link[AzureAuth:AzureToken]{AzureAuth::AzureToken}. If supplied, the authentication details for the registry will be inferred from this.} 19 | 20 | \item{login}{Whether to perform a local login (requires that you have Docker installed). This is necessary if you want to push or pull images.} 21 | } 22 | \value{ 23 | An R6 object of class \code{DockerRegistry}. 24 | } 25 | \description{ 26 | Create a new Docker registry object 27 | } 28 | \details{ 29 | There are two ways to authenticate with an Azure Docker registry: via Azure Active Directory (AAD), or with a username and password. The latter is simpler, while the former is more complex but also more flexible and secure. 30 | 31 | The default method of authenticating is via AAD. Without any arguments, \code{docker_registry} will authenticate using the AAD credentials of the currently logged-in user. You can change this by supplying the appropriate arguments to \code{docker_registry}, which will be passed to \code{AzureAuth::get_azure_token}; alternatively, you can provide an existing token object. 32 | 33 | To authenticate via the admin user account, set \code{app=NULL} and supply the admin username and password in the corresponding arguments. Note that for this to work, the registry must have been created with the admin account enabled. 34 | 35 | Authenticating with a service principal can be done either indirectly via AAD, or via a username and password. See the examples below. The latter method is recommended, as it is both faster and allows easier interoperability with AKS and ACI. 36 | } 37 | \examples{ 38 | \dontrun{ 39 | 40 | # connect to the Docker registry 'myregistry.azurecr.io', authenticating as the current user 41 | docker_registry("myregistry") 42 | 43 | # same, but providing a full URL 44 | docker_registry("https://myregistry.azurecr.io") 45 | 46 | # authenticating via the admin account 47 | docker_registry("myregistry", username="admin", password="password", app=NULL) 48 | 49 | # authenticating with a service principal, method 1: recommended 50 | docker_registry("myregistry", username="app_id", password="client_creds", app=NULL) 51 | 52 | # authenticating with a service principal, method 2 53 | docker_registry("myregistry", app="app_id", password="client_creds") 54 | 55 | # authenticating from a managed service identity (MSI) 56 | token <- AzureAuth::get_managed_token("https://management.azure.com/") 57 | docker_registry("myregistry", token=token) 58 | 59 | # you can also interact with a registry outside Azure 60 | # note that some registry methods, and AAD authentication, may not work in this case 61 | docker_registry("https://hub.docker.com", username="mydockerid", password="password", app=NULL) 62 | 63 | } 64 | } 65 | \seealso{ 66 | \link{DockerRegistry} for methods available for interacting with the registry, \link{call_docker} 67 | 68 | \link{kubernetes_cluster} for the corresponding function to create a Kubernetes cluster object 69 | } 70 | -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudyr/AzureContainers/e1c3644eb2dd4e4167ad79d5816f1ef06a7011eb/man/figures/logo.png -------------------------------------------------------------------------------- /man/get_aci.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_aci_methods.R 3 | \name{get_aci} 4 | \alias{get_aci} 5 | \alias{list_acis} 6 | \title{Get Azure Container Instance (ACI)} 7 | \description{ 8 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 9 | } 10 | \section{Usage}{ 11 | 12 | 13 | \if{html}{\out{
}}\preformatted{get_aci(name) 14 | list_acis() 15 | }\if{html}{\out{
}} 16 | } 17 | 18 | \section{Arguments}{ 19 | 20 | \itemize{ 21 | \item \code{name}: For \code{get_aci()}, the name of the container instance resource. 22 | } 23 | } 24 | 25 | \section{Details}{ 26 | 27 | The \code{AzureRMR::az_resource_group} class has both \code{get_aci()} and \code{list_acis()} methods, while the \code{AzureRMR::az_subscription} class only has the latter. 28 | } 29 | 30 | \section{Value}{ 31 | 32 | For \code{get_aci()}, an object of class \code{az_container_instance} representing the instance resource. 33 | 34 | For \code{list_acis()}, a list of such objects. 35 | } 36 | 37 | \examples{ 38 | \dontrun{ 39 | 40 | rg <- AzureRMR::get_azure_login()$ 41 | get_subscription("subscription_id")$ 42 | get_resource_group("rgname") 43 | 44 | rg$get_aci("mycontainer") 45 | 46 | } 47 | } 48 | \seealso{ 49 | \link{create_aci}, \link{delete_aci} 50 | 51 | \link{az_container_instance} 52 | 53 | \href{https://learn.microsoft.com/en-us/azure/container-instances/}{ACI documentation} and 54 | \href{https://learn.microsoft.com/en-us/rest/api/container-instances/}{API reference} 55 | 56 | \href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker commandline reference} 57 | } 58 | -------------------------------------------------------------------------------- /man/get_acr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_acr_methods.R 3 | \name{get_acr} 4 | \alias{get_acr} 5 | \alias{list_acrs} 6 | \title{Get Azure Container Registry (ACR)} 7 | \description{ 8 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 9 | } 10 | \section{Usage}{ 11 | 12 | 13 | \if{html}{\out{
}}\preformatted{get_acr(name) 14 | list_acrs() 15 | }\if{html}{\out{
}} 16 | } 17 | 18 | \section{Arguments}{ 19 | 20 | \itemize{ 21 | \item \code{name}: For \code{get_acr()}, the name of the container registry resource. 22 | } 23 | } 24 | 25 | \section{Details}{ 26 | 27 | The \code{AzureRMR::az_resource_group} class has both \code{get_acr()} and \code{list_acrs()} methods, while the \code{AzureRMR::az_subscription} class only has the latter. 28 | } 29 | 30 | \section{Value}{ 31 | 32 | For \code{get_acr()}, an object of class \code{az_container_registry} representing the registry resource. 33 | 34 | For \code{list_acrs()}, a list of such objects. 35 | } 36 | 37 | \examples{ 38 | \dontrun{ 39 | 40 | rg <- AzureRMR::get_azure_login()$ 41 | get_subscription("subscription_id")$ 42 | get_resource_group("rgname") 43 | 44 | rg$get_acr("myregistry") 45 | 46 | } 47 | } 48 | \seealso{ 49 | \link{create_acr}, \link{delete_acr} 50 | 51 | \link{az_container_registry} 52 | 53 | \link{docker_registry} for the registry endpoint 54 | 55 | \href{https://learn.microsoft.com/en-us/azure/container-registry/}{ACR documentation} and 56 | \href{https://learn.microsoft.com/en-us/rest/api/containerregistry/registries}{API reference} 57 | 58 | \href{https://docs.docker.com/registry/spec/api/}{Docker registry API} 59 | } 60 | -------------------------------------------------------------------------------- /man/get_aks.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_aks_methods.R 3 | \name{get_aks} 4 | \alias{get_aks} 5 | \alias{list_aks} 6 | \title{Get Azure Kubernetes Service (AKS)} 7 | \description{ 8 | Method for the \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} class. 9 | } 10 | \section{Usage}{ 11 | 12 | 13 | \if{html}{\out{
}}\preformatted{get_aks(name) 14 | list_aks() 15 | }\if{html}{\out{
}} 16 | } 17 | 18 | \section{Arguments}{ 19 | 20 | \itemize{ 21 | \item \code{name}: For \code{get_aks()}, the name of the Kubernetes service. 22 | } 23 | } 24 | 25 | \section{Details}{ 26 | 27 | The \code{AzureRMR::az_resource_group} class has both \code{get_aks()} and \code{list_aks()} methods, while the \code{AzureRMR::az_subscription} class only has the latter. 28 | } 29 | 30 | \section{Value}{ 31 | 32 | For \code{get_aks()}, an object of class \code{az_kubernetes_service} representing the service. 33 | 34 | For \code{list_aks()}, a list of such objects. 35 | } 36 | 37 | \examples{ 38 | \dontrun{ 39 | 40 | rg <- AzureRMR::get_azure_login()$ 41 | get_subscription("subscription_id")$ 42 | get_resource_group("rgname") 43 | 44 | rg$get_aks("mycluster") 45 | 46 | } 47 | } 48 | \seealso{ 49 | \link{create_aks}, \link{delete_aks} 50 | 51 | \link{az_kubernetes_service} 52 | 53 | \link{kubernetes_cluster} for the cluster endpoint 54 | 55 | \href{https://learn.microsoft.com/en-us/azure/aks/}{AKS documentation} and 56 | \href{https://learn.microsoft.com/en-us/rest/api/aks/}{API reference} 57 | 58 | \href{https://kubernetes.io/docs/reference/}{Kubernetes reference} 59 | } 60 | -------------------------------------------------------------------------------- /man/is.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/is.R 3 | \name{is_acr} 4 | \alias{is_acr} 5 | \alias{is_aks} 6 | \alias{is_aci} 7 | \alias{is_docker_registry} 8 | \alias{is_kubernetes_cluster} 9 | \title{Utility functions to test whether an object is of the given class.} 10 | \usage{ 11 | is_acr(object) 12 | 13 | is_aks(object) 14 | 15 | is_aci(object) 16 | 17 | is_docker_registry(object) 18 | 19 | is_kubernetes_cluster(object) 20 | } 21 | \arguments{ 22 | \item{object}{An R object} 23 | } 24 | \value{ 25 | TRUE or FALSE depending on whether the object is an R6 object of the specified class. 26 | } 27 | \description{ 28 | Utility functions to test whether an object is of the given class. 29 | } 30 | \details{ 31 | These functions are simple wrappers around \code{R6::is.R6} and \code{inherits}. 32 | } 33 | -------------------------------------------------------------------------------- /man/kubernetes_cluster.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/kubernetes_cluster.R 3 | \name{kubernetes_cluster} 4 | \alias{kubernetes_cluster} 5 | \title{Create a new Kubernetes cluster object} 6 | \usage{ 7 | kubernetes_cluster(config = NULL) 8 | } 9 | \arguments{ 10 | \item{config}{The name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default \verb{~/.kube/config} file.} 11 | } 12 | \value{ 13 | An R6 object of class \code{KubernetesCluster}. 14 | } 15 | \description{ 16 | Create a new Kubernetes cluster object 17 | } 18 | \details{ 19 | Use this function to instantiate a new object of the \code{KubernetesCluster} class, for interacting with a Kubernetes cluster. 20 | } 21 | \examples{ 22 | \dontrun{ 23 | 24 | kubernetes_cluster() 25 | kubernetes_cluster("myconfig.yaml") 26 | 27 | } 28 | } 29 | \seealso{ 30 | \link{KubernetesCluster} for methods for working with the cluster, \link{call_kubectl}, \link{call_helm} 31 | 32 | \link{docker_registry} for the corresponding function to create a Docker registry object 33 | } 34 | -------------------------------------------------------------------------------- /man/list_kubernetes_versions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_aks_methods.R 3 | \name{list_kubernetes_versions} 4 | \alias{list_kubernetes_versions} 5 | \title{List available Kubernetes versions} 6 | \description{ 7 | Method for the \link[AzureRMR:az_subscription]{AzureRMR::az_subscription} and \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} classes. 8 | } 9 | \section{Usage}{ 10 | 11 | 12 | \if{html}{\out{
}}\preformatted{## R6 method for class 'az_subscription' 13 | list_kubernetes_versions(location) 14 | 15 | ## R6 method for class 'az_resource_group' 16 | list_kubernetes_versions() 17 | }\if{html}{\out{
}} 18 | } 19 | 20 | \section{Arguments}{ 21 | 22 | \itemize{ 23 | \item \code{location}: For the az_subscription class method, the location for which to obtain available Kubernetes versions. 24 | } 25 | } 26 | 27 | \section{Value}{ 28 | 29 | A vector of strings, which are the Kubernetes versions that can be used when creating a cluster. 30 | } 31 | 32 | \examples{ 33 | \dontrun{ 34 | 35 | rg <- AzureRMR::get_azure_login()$ 36 | get_subscription("subscription_id")$ 37 | get_resource_group("rgname") 38 | 39 | rg$list_kubernetes_versions() 40 | 41 | } 42 | } 43 | \seealso{ 44 | \link{create_aks} 45 | 46 | \href{https://kubernetes.io/docs/reference/}{Kubernetes reference} 47 | } 48 | -------------------------------------------------------------------------------- /man/list_vm_sizes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/add_vmsize_methods.R 3 | \name{list_vm_sizes} 4 | \alias{list_vm_sizes} 5 | \title{List available VM sizes} 6 | \description{ 7 | Method for the \link[AzureRMR:az_subscription]{AzureRMR::az_subscription} and \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} classes. 8 | } 9 | \section{Usage}{ 10 | 11 | 12 | \if{html}{\out{
}}\preformatted{## R6 method for class 'az_subscription' 13 | list_vm_sizes(location, name_only = FALSE) 14 | 15 | ## R6 method for class 'az_resource_group' 16 | list_vm_sizes(name_only = FALSE) 17 | }\if{html}{\out{
}} 18 | } 19 | 20 | \section{Arguments}{ 21 | 22 | \itemize{ 23 | \item \code{location}: For the subscription class method, the location/region for which to obtain available VM sizes. 24 | \item \code{name_only}: Whether to return only a vector of names, or all information on each VM size. 25 | } 26 | } 27 | 28 | \section{Value}{ 29 | 30 | If \code{name_only} is TRUE, a character vector of names. If FALSE, a data frame containing the following information for each VM size: the name, number of cores, OS disk size, resource disk size, memory, and maximum data disks. 31 | } 32 | 33 | \examples{ 34 | \dontrun{ 35 | 36 | sub <- AzureRMR::get_azure_login()$ 37 | get_subscription("subscription_id") 38 | 39 | sub$list_vm_sizes("australiaeast") 40 | 41 | # same output as above 42 | rg <- sub$create_resource_group("rgname", location="australiaeast") 43 | rg$list_vm_sizes() 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/resources/hello.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: hellodep 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: hellodep 9 | replicas: 1 10 | template: 11 | metadata: 12 | labels: 13 | app: hellodep 14 | spec: 15 | containers: 16 | - name: acrname 17 | image: acrname.azurecr.io/hello-world 18 | imagePullSecrets: 19 | - name: acrname.azurecr.io 20 | --- 21 | apiVersion: v1 22 | kind: Service 23 | metadata: 24 | name: hellodep-svc 25 | spec: 26 | selector: 27 | app: hellodep 28 | type: LoadBalancer 29 | ports: 30 | - protocol: TCP 31 | port: 80 32 | -------------------------------------------------------------------------------- /tests/resources/hello_dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(AzureContainers) 3 | 4 | test_check("AzureContainers") 5 | -------------------------------------------------------------------------------- /tests/testthat/setup.R: -------------------------------------------------------------------------------- 1 | make_name <- function(n=20) 2 | { 3 | paste0(sample(letters, n, TRUE), collapse="") 4 | } 5 | -------------------------------------------------------------------------------- /tests/testthat/test00_tools.R: -------------------------------------------------------------------------------- 1 | context("Tools interface") 2 | 3 | if(.AzureContainers$docker == "" || 4 | .AzureContainers$dockercompose == "" || 5 | .AzureContainers$kubectl == "" || 6 | .AzureContainers$helm == "") 7 | skip("Tests skipped: external tools not found") 8 | 9 | echo <- getOption("azure_containers_tool_echo") 10 | options(azure_containers_tool_echo=FALSE) 11 | 12 | test_that("Docker works", 13 | { 14 | cmd <- "--help" 15 | obj <- call_docker(cmd) 16 | expect_is(obj, "list") 17 | expect_true(grepl("docker --help", obj$cmdline, fixed=TRUE)) 18 | }) 19 | 20 | test_that("Docker compose works", 21 | { 22 | cmd <- "--help" 23 | obj <- call_docker_compose(cmd) 24 | expect_is(obj, "list") 25 | expect_true(grepl("docker-compose --help", obj$cmdline, fixed=TRUE)) 26 | }) 27 | 28 | test_that("Kubectl works", 29 | { 30 | cmd <- "--help" 31 | obj <- call_kubectl(cmd) 32 | expect_is(obj, "list") 33 | expect_true(grepl("kubectl --help", obj$cmdline, fixed=TRUE)) 34 | }) 35 | 36 | test_that("Helm works", 37 | { 38 | cmd <- "--help" 39 | obj <- call_helm(cmd) 40 | expect_is(obj, "list") 41 | expect_true(grepl("helm --help", obj$cmdline, fixed=TRUE)) 42 | }) 43 | 44 | teardown({ 45 | options(azure_containers_tool_echo=echo) 46 | }) 47 | -------------------------------------------------------------------------------- /tests/testthat/test01_acr.R: -------------------------------------------------------------------------------- 1 | context("ACR interface") 2 | 3 | tenant <- Sys.getenv("AZ_TEST_TENANT_ID") 4 | app <- Sys.getenv("AZ_TEST_APP_ID") 5 | password <- Sys.getenv("AZ_TEST_PASSWORD") 6 | subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") 7 | 8 | if(tenant == "" || app == "" || password == "" || subscription == "") 9 | skip("Tests skipped: ARM credentials not set") 10 | 11 | rgname <- make_name(10) 12 | rg <- AzureRMR::az_rm$ 13 | new(tenant=tenant, app=app, password=password)$ 14 | get_subscription(subscription)$ 15 | create_resource_group(rgname, location="australiaeast") 16 | 17 | echo <- getOption("azure_containers_tool_echo") 18 | options(azure_containers_tool_echo=FALSE) 19 | 20 | acrname <- make_name(10) 21 | 22 | test_that("ACR works", 23 | { 24 | expect_true(is_acr(rg$create_acr(acrname))) 25 | acr <- rg$get_acr(acrname) 26 | expect_true(is_acr(acr)) 27 | 28 | acr2 <- rg$list_acrs()[[1]] 29 | expect_true(is_acr(acr2)) 30 | 31 | reg <- acr$get_docker_registry() 32 | expect_true(is_docker_registry(reg)) 33 | 34 | cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ." 35 | output <- call_docker(cmdline) 36 | expect_equal(output$cmdline, paste("docker", cmdline)) 37 | 38 | reg$push("hello-world") 39 | 40 | cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world") 41 | call_docker(cmdline) 42 | 43 | expect_equal(reg$list_repositories(), "hello-world") 44 | }) 45 | 46 | test_that("ACR works with app login", 47 | { 48 | acr <- rg$get_acr(acrname) 49 | expect_true(is_acr(acr)) 50 | 51 | acr$add_role_assignment( 52 | principal=AzureGraph::get_graph_login(tenant)$get_app(app), 53 | role="owner" 54 | ) 55 | 56 | reg <- acr$get_docker_registry(username=app, password=password, app=NULL) 57 | expect_true(is_docker_registry(reg)) 58 | expect_identical(reg$username, app) 59 | 60 | cmdline <- "build -f ../resources/hello_dockerfile -t hello-world-sp ." 61 | output <- call_docker(cmdline) 62 | expect_equal(output$cmdline, paste("docker", cmdline)) 63 | 64 | reg$push("hello-world-sp") 65 | 66 | cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world-sp") 67 | call_docker(cmdline) 68 | 69 | expect_equal(reg$list_repositories(), c("hello-world", "hello-world-sp")) 70 | }) 71 | 72 | teardown({ 73 | options(azure_containers_tool_echo=echo) 74 | suppressMessages(rg$delete(confirm=FALSE)) 75 | }) 76 | -------------------------------------------------------------------------------- /tests/testthat/test02_aci.R: -------------------------------------------------------------------------------- 1 | context("ACI interface") 2 | 3 | tenant <- Sys.getenv("AZ_TEST_TENANT_ID") 4 | app <- Sys.getenv("AZ_TEST_APP_ID") 5 | password <- Sys.getenv("AZ_TEST_PASSWORD") 6 | subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") 7 | 8 | if(tenant == "" || app == "" || password == "" || subscription == "") 9 | skip("Tests skipped: ARM credentials not set") 10 | 11 | rgname <- make_name(10) 12 | rg <- AzureRMR::az_rm$ 13 | new(tenant=tenant, app=app, password=password)$ 14 | get_subscription(subscription)$ 15 | create_resource_group(rgname, location="australiaeast") 16 | 17 | echo <- getOption("azure_containers_tool_echo") 18 | options(azure_containers_tool_echo=FALSE) 19 | 20 | test_that("ACI works", 21 | { 22 | acrname <- make_name(10) 23 | acr <- rg$create_acr(acrname, admin_user_enabled=TRUE) 24 | reg <- acr$get_docker_registry(as_admin=TRUE) 25 | expect_true(is_docker_registry(reg)) 26 | expect_false(is.null(reg$username) || is.null(reg$password)) 27 | 28 | cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ." 29 | call_docker(cmdline) 30 | 31 | reg$push("hello-world") 32 | 33 | cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world") 34 | call_docker(cmdline) 35 | 36 | # from local image 37 | aciname <- make_name(10) 38 | expect_true(is_aci(rg$create_aci(aciname, 39 | image="hello-world", 40 | env_vars=list(MYVAR="myvalue")), 41 | secure_env_vars=list(MYSECUREVAR="mysecurevalue"), 42 | command=c("/bin/sh", "-c", "ls")) 43 | ) 44 | 45 | aci <- rg$get_aci(aciname) 46 | expect_true(is_aci(aci)) 47 | expect_true(is_aci(rg$list_acis()[[1]])) 48 | 49 | expect_silent(aci$stop()) 50 | Sys.sleep(2) 51 | expect_silent(aci$start()) 52 | Sys.sleep(2) 53 | expect_silent(aci$restart()) 54 | 55 | # from Resource Manager object 56 | aciname2 <- make_name(10) 57 | aci2 <- rg$create_aci(aciname2, 58 | image=paste0(reg$server$hostname, "/hello-world"), 59 | registry_creds=reg) 60 | 61 | expect_true(is_aci(aci2)) 62 | 63 | # from Docker registry object 64 | aciname3 <- make_name(10) 65 | aci3 <- rg$create_aci(aciname3, 66 | image=paste0(reg$server$hostname, "/hello-world"), 67 | registry_creds=aci_creds(reg$server$hostname, app, password)) 68 | 69 | expect_true(is_aci(aci3)) 70 | }) 71 | 72 | 73 | teardown({ 74 | options(azure_containers_tool_echo=echo) 75 | suppressMessages(rg$delete(confirm=FALSE)) 76 | }) 77 | -------------------------------------------------------------------------------- /tests/testthat/test03a_aks_msi.R: -------------------------------------------------------------------------------- 1 | context("AKS interface with managed identity") 2 | 3 | tenant <- Sys.getenv("AZ_TEST_TENANT_ID") 4 | app <- Sys.getenv("AZ_TEST_APP_ID") 5 | password <- Sys.getenv("AZ_TEST_PASSWORD") 6 | subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") 7 | 8 | if(tenant == "" || app == "" || password == "" || subscription == "") 9 | skip("Tests skipped: ARM credentials not set") 10 | 11 | rgname <- make_name(10) 12 | rg <- AzureRMR::az_rm$ 13 | new(tenant=tenant, app=app, password=password)$ 14 | get_subscription(subscription)$ 15 | create_resource_group(rgname, location="australiaeast") 16 | 17 | echo <- getOption("azure_containers_tool_echo") 18 | options(azure_containers_tool_echo=FALSE) 19 | 20 | test_that("AKS works with managed identity", 21 | { 22 | aksname <- make_name(10) 23 | 24 | expect_is(rg$list_kubernetes_versions(), "character") 25 | 26 | expect_true(is_aks(rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), managed_identity=TRUE))) 27 | expect_true(is_aks(rg$list_aks()[[1]])) 28 | aks <- rg$get_aks(aksname) 29 | expect_true(is_aks(aks)) 30 | 31 | # no SP password with svc identity 32 | expect_error(aks$update_service_password()) 33 | 34 | pool1 <- aks$get_agent_pool("pool1") 35 | expect_is(pool1, "az_agent_pool") 36 | 37 | pools <- aks$list_agent_pools() 38 | expect_true(is.list(pools) && length(pools) == 1 && all(sapply(pools, inherits, "az_agent_pool"))) 39 | 40 | pool2 <- aks$create_agent_pool("pool2", 2, disksize=500, wait=TRUE, autoscale_nodes=c(1, 5)) 41 | expect_is(pool2, "az_agent_pool") 42 | expect_true(pool2$properties$enableAutoScaling) 43 | expect_equal(pool2$properties$minCount, 1) 44 | expect_equal(pool2$properties$maxCount, 5) 45 | 46 | pools <- aks$list_agent_pools() 47 | expect_true(is.list(pools) && length(pools) == 2 && all(sapply(pools, inherits, "az_agent_pool"))) 48 | 49 | expect_message(pool2$delete(confirm=FALSE)) 50 | 51 | clus <- aks$get_cluster() 52 | expect_true(is_kubernetes_cluster(clus)) 53 | }) 54 | 55 | 56 | teardown({ 57 | options(azure_containers_tool_echo=echo) 58 | suppressMessages(rg$delete(confirm=FALSE)) 59 | }) 60 | -------------------------------------------------------------------------------- /tests/testthat/test03b_aks_acr_msi.R: -------------------------------------------------------------------------------- 1 | context("AKS-ACR interop with managed identity") 2 | 3 | tenant <- Sys.getenv("AZ_TEST_TENANT_ID") 4 | app <- Sys.getenv("AZ_TEST_APP_ID") 5 | password <- Sys.getenv("AZ_TEST_PASSWORD") 6 | subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") 7 | 8 | if(tenant == "" || app == "" || password == "" || subscription == "") 9 | skip("Tests skipped: ARM credentials not set") 10 | 11 | rgname <- make_name(10) 12 | rg <- AzureRMR::az_rm$ 13 | new(tenant=tenant, app=app, password=password)$ 14 | get_subscription(subscription)$ 15 | create_resource_group(rgname, location="australiaeast") 16 | 17 | echo <- getOption("azure_containers_tool_echo") 18 | options(azure_containers_tool_echo=FALSE) 19 | 20 | aksname <- make_name(10) 21 | aks <- rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), managed_identity=TRUE) 22 | 23 | test_that("AKS/ACR works with managed identity", 24 | { 25 | acrname <- make_name(10) 26 | acr <- rg$create_acr(acrname, admin_user_enabled=TRUE) 27 | reg <- acr$get_docker_registry(as_admin=TRUE) 28 | expect_true(is_docker_registry(reg)) 29 | 30 | cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ." 31 | call_docker(cmdline) 32 | 33 | reg$push("hello-world") 34 | 35 | cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world") 36 | call_docker(cmdline) 37 | 38 | expect_true(is_aks(aks)) 39 | 40 | clus <- aks$get_cluster() 41 | expect_true(is_kubernetes_cluster(clus)) 42 | 43 | hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml")) 44 | clus$create_registry_secret(reg, email="me@example.com") 45 | clus$create(hello_yaml) 46 | }) 47 | 48 | 49 | test_that("AKS/ACR works with managed identity/RBAC", 50 | { 51 | acrname <- make_name(10) 52 | acr <- rg$create_acr(acrname, admin_user_enabled=FALSE) 53 | reg <- acr$get_docker_registry(as_admin=FALSE) 54 | expect_true(is_docker_registry(reg)) 55 | 56 | cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ." 57 | call_docker(cmdline) 58 | 59 | reg$push("hello-world") 60 | 61 | cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world") 62 | call_docker(cmdline) 63 | 64 | acr$add_role_assignment(aks, "Acrpull") 65 | 66 | clus <- aks$get_cluster() 67 | expect_true(is_kubernetes_cluster(clus)) 68 | 69 | hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml")) 70 | hello_yaml <- gsub("hellodep", "hellodep-rb", hello_yaml) 71 | clus$create(hello_yaml) 72 | }) 73 | 74 | 75 | teardown({ 76 | options(azure_containers_tool_echo=echo) 77 | suppressMessages(rg$delete(confirm=FALSE)) 78 | }) 79 | -------------------------------------------------------------------------------- /tests/testthat/test03c_aks_sp.R: -------------------------------------------------------------------------------- 1 | context("AKS interface with service principal") 2 | 3 | tenant <- Sys.getenv("AZ_TEST_TENANT_ID") 4 | app <- Sys.getenv("AZ_TEST_APP_ID") 5 | password <- Sys.getenv("AZ_TEST_PASSWORD") 6 | subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") 7 | 8 | if(tenant == "" || app == "" || password == "" || subscription == "") 9 | skip("Tests skipped: ARM credentials not set") 10 | 11 | rgname <- make_name(10) 12 | rg <- AzureRMR::az_rm$ 13 | new(tenant=tenant, app=app, password=password)$ 14 | get_subscription(subscription)$ 15 | create_resource_group(rgname, location="australiaeast") 16 | 17 | echo <- getOption("azure_containers_tool_echo") 18 | options(azure_containers_tool_echo=FALSE) 19 | 20 | test_that("AKS works with service principal", 21 | { 22 | aksname <- paste0(sample(letters, 10, TRUE), collapse="") 23 | expect_true(is_aks(rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), managed_identity=FALSE))) 24 | aks <- rg$get_aks(aksname) 25 | expect_true(is_aks(aks)) 26 | 27 | expect_message(aks$update_service_password()) 28 | 29 | pool1 <- aks$get_agent_pool("pool1") 30 | expect_is(pool1, "az_agent_pool") 31 | 32 | pools <- aks$list_agent_pools() 33 | expect_true(is.list(pools) && length(pools) == 1 && all(sapply(pools, inherits, "az_agent_pool"))) 34 | 35 | pool2 <- aks$create_agent_pool("pool2", 1, disksize=500, wait=TRUE) 36 | expect_is(pool2, "az_agent_pool") 37 | 38 | pools <- aks$list_agent_pools() 39 | expect_true(is.list(pools) && length(pools) == 2 && all(sapply(pools, inherits, "az_agent_pool"))) 40 | 41 | expect_message(pool2$delete(confirm=FALSE)) 42 | 43 | clus <- aks$get_cluster() 44 | expect_true(is_kubernetes_cluster(clus)) 45 | }) 46 | 47 | 48 | teardown({ 49 | options(azure_containers_tool_echo=echo) 50 | suppressMessages(rg$delete(confirm=FALSE)) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/testthat/test03d_aks_acr_sp.R: -------------------------------------------------------------------------------- 1 | context("AKS-ACR interop with service principal") 2 | 3 | tenant <- Sys.getenv("AZ_TEST_TENANT_ID") 4 | app <- Sys.getenv("AZ_TEST_APP_ID") 5 | password <- Sys.getenv("AZ_TEST_PASSWORD") 6 | subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") 7 | 8 | if(tenant == "" || app == "" || password == "" || subscription == "") 9 | skip("Tests skipped: ARM credentials not set") 10 | 11 | rgname <- make_name(10) 12 | rg <- AzureRMR::az_rm$ 13 | new(tenant=tenant, app=app, password=password)$ 14 | get_subscription(subscription)$ 15 | create_resource_group(rgname, location="australiaeast") 16 | 17 | echo <- getOption("azure_containers_tool_echo") 18 | options(azure_containers_tool_echo=FALSE) 19 | 20 | aksname <- make_name(10) 21 | aks <- rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), managed_identity=FALSE) 22 | 23 | test_that("AKS/ACR works with service principal", 24 | { 25 | acrname <- make_name(10) 26 | acr <- rg$create_acr(acrname, admin_user_enabled=TRUE) 27 | reg <- acr$get_docker_registry(as_admin=TRUE) 28 | expect_true(is_docker_registry(reg)) 29 | 30 | cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ." 31 | call_docker(cmdline) 32 | 33 | reg$push("hello-world") 34 | 35 | cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world") 36 | call_docker(cmdline) 37 | 38 | expect_true(is_aks(aks)) 39 | 40 | clus <- aks$get_cluster() 41 | expect_true(is_kubernetes_cluster(clus)) 42 | 43 | hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml")) 44 | clus$create_registry_secret(reg, email="me@example.com") 45 | clus$create(hello_yaml) 46 | }) 47 | 48 | 49 | test_that("AKS/ACR works with service principal/RBAC", 50 | { 51 | acrname <- make_name(10) 52 | acr <- rg$create_acr(acrname, admin_user_enabled=FALSE) 53 | reg <- acr$get_docker_registry(as_admin=FALSE) 54 | expect_true(is_docker_registry(reg)) 55 | 56 | cmdline <- "build -f ../resources/hello_dockerfile -t hello-world ." 57 | call_docker(cmdline) 58 | 59 | reg$push("hello-world") 60 | 61 | cmdline <- paste0("image rm ", acrname, ".azurecr.io/hello-world") 62 | call_docker(cmdline) 63 | 64 | acr$add_role_assignment(aks, "Acrpull") 65 | 66 | clus <- aks$get_cluster() 67 | expect_true(is_kubernetes_cluster(clus)) 68 | 69 | hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml")) 70 | hello_yaml <- gsub("hellodep", "hellodep-rb", hello_yaml) 71 | clus$create(hello_yaml) 72 | }) 73 | 74 | 75 | teardown({ 76 | options(azure_containers_tool_echo=echo) 77 | suppressMessages(rg$delete(confirm=FALSE)) 78 | }) 79 | -------------------------------------------------------------------------------- /tests/testthat/test03e_aks_oldnodes.R: -------------------------------------------------------------------------------- 1 | context("AKS interface with availability set") 2 | 3 | tenant <- Sys.getenv("AZ_TEST_TENANT_ID") 4 | app <- Sys.getenv("AZ_TEST_APP_ID") 5 | password <- Sys.getenv("AZ_TEST_PASSWORD") 6 | subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") 7 | 8 | if(tenant == "" || app == "" || password == "" || subscription == "") 9 | skip("Tests skipped: ARM credentials not set") 10 | 11 | rgname <- make_name(10) 12 | rg <- AzureRMR::az_rm$ 13 | new(tenant=tenant, app=app, password=password)$ 14 | get_subscription(subscription)$ 15 | create_resource_group(rgname, location="australiaeast") 16 | 17 | echo <- getOption("azure_containers_tool_echo") 18 | options(azure_containers_tool_echo=FALSE) 19 | 20 | test_that("AKS works with availability set", 21 | { 22 | aksname <- make_name(10) 23 | expect_true(is_aks(rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1, use_scaleset=FALSE), 24 | managed_identity=TRUE))) 25 | 26 | aks <- rg$get_aks(aksname) 27 | expect_true(is_aks(aks)) 28 | 29 | pool1 <- aks$get_agent_pool("pool1") 30 | expect_is(pool1, "az_agent_pool") 31 | 32 | pools <- aks$list_agent_pools() 33 | expect_true(is.list(pools) && length(pools) == 1 && all(sapply(pools, inherits, "az_agent_pool"))) 34 | 35 | expect_error(aks$create_agent_pool("pool2", 1, disksize=500, wait=TRUE)) 36 | 37 | clus <- aks$get_cluster() 38 | expect_true(is_kubernetes_cluster(clus)) 39 | }) 40 | 41 | 42 | teardown({ 43 | options(azure_containers_tool_echo=echo) 44 | suppressMessages(rg$delete(confirm=FALSE)) 45 | }) 46 | -------------------------------------------------------------------------------- /tests/testthat/test03f_aks_private.R: -------------------------------------------------------------------------------- 1 | context("AKS interface with managed identity/private cluster") 2 | 3 | tenant <- Sys.getenv("AZ_TEST_TENANT_ID") 4 | app <- Sys.getenv("AZ_TEST_APP_ID") 5 | password <- Sys.getenv("AZ_TEST_PASSWORD") 6 | subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") 7 | 8 | if(tenant == "" || app == "" || password == "" || subscription == "") 9 | skip("Tests skipped: ARM credentials not set") 10 | 11 | rgname <- make_name(10) 12 | rg <- AzureRMR::az_rm$ 13 | new(tenant=tenant, app=app, password=password)$ 14 | get_subscription(subscription)$ 15 | create_resource_group(rgname, location="australiaeast") 16 | 17 | echo <- getOption("azure_containers_tool_echo") 18 | options(azure_containers_tool_echo=FALSE) 19 | 20 | test_that("AKS works with private cluster", 21 | { 22 | aksname <- make_name(10) 23 | expect_true(is_aks(rg$create_aks(aksname, agent_pools=agent_pool("pool1", 1), 24 | managed_identity=TRUE, private_cluster=TRUE))) 25 | aks <- rg$get_aks(aksname) 26 | expect_true(is_aks(aks)) 27 | 28 | # no SP password with svc identity 29 | expect_error(aks$update_service_password()) 30 | 31 | pool1 <- aks$get_agent_pool("pool1") 32 | expect_is(pool1, "az_agent_pool") 33 | 34 | pools <- aks$list_agent_pools() 35 | expect_true(is.list(pools) && length(pools) == 1 && all(sapply(pools, inherits, "az_agent_pool"))) 36 | 37 | clus <- aks$get_cluster() 38 | expect_true(is_kubernetes_cluster(clus)) 39 | }) 40 | 41 | 42 | teardown({ 43 | options(azure_containers_tool_echo=echo) 44 | suppressMessages(rg$delete(confirm=FALSE)) 45 | }) 46 | -------------------------------------------------------------------------------- /vignettes/perms2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudyr/AzureContainers/e1c3644eb2dd4e4167ad79d5816f1ef06a7011eb/vignettes/perms2.png -------------------------------------------------------------------------------- /vignettes/vig01_plumber_deploy.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Deploying a prediction service with Plumber" 3 | Author: Hong Ooi 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Plumber model deployment} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{utf8} 9 | --- 10 | 11 | This document shows how you can deploy a fitted model as a web service using ACR, ACI and AKS. The framework used is [Plumber](https://www.rplumber.io), a package to expose your R code as a service via a REST API. 12 | 13 | 14 | ## Fit the model 15 | 16 | We'll fit a simple model for illustrative purposes, using the Boston housing dataset which ships with R (in the MASS package). To make the deployment process more interesting, the model we fit will be a random forest, using the randomForest package. 17 | 18 | ```r 19 | data(Boston, package="MASS") 20 | library(randomForest) 21 | 22 | # train a model for median house price as a function of the other variables 23 | bos_rf <- randomForest(medv ~ ., data=Boston, ntree=100) 24 | 25 | # save the model 26 | saveRDS(bos_rf, "bos_rf.rds") 27 | ``` 28 | 29 | ## Scoring script for plumber 30 | 31 | Now that we have the model, we also need a script to obtain predicted values from it given a set of inputs: 32 | 33 | ```r 34 | # save as bos_rf_score.R 35 | 36 | bos_rf <- readRDS("bos_rf.rds") 37 | library(randomForest) 38 | 39 | #* @param df data frame of variables 40 | #* @post /score 41 | function(req, df) 42 | { 43 | df <- as.data.frame(df) 44 | predict(bos_rf, df) 45 | } 46 | ``` 47 | 48 | This is fairly straightforward, but the comments may require some explanation. They are plumber annotations that tell it to call the function if the server receives a HTTP POST request with the path `/score`, and query parameter `df`. The value of the `df` parameter is then converted to a data frame, and passed to the randomForest `predict` method. 49 | 50 | 51 | ## Create a Dockerfile 52 | 53 | Let's package up the model and the scoring script into a Docker image. A Dockerfile to do this would look like the following. This uses the base image supplied by Plumber (`trestletech/plumber`), installs randomForest, and then adds the model and the above scoring script. Finally, it runs the code that will start the server and listen on port 8000. 54 | 55 | ```dockerfile 56 | # example Dockerfile to expose a plumber service 57 | 58 | FROM trestletech/plumber 59 | 60 | # install the randomForest package 61 | RUN R -e 'install.packages(c("randomForest"))' 62 | 63 | # copy model and scoring script 64 | RUN mkdir /data 65 | COPY bos_rf.rds /data 66 | COPY bos_rf_score.R /data 67 | WORKDIR /data 68 | 69 | # plumb and run server 70 | EXPOSE 8000 71 | ENTRYPOINT ["R", "-e", \ 72 | "pr <- plumber::plumb('/data/bos_rf_score.R'); pr$run(host='0.0.0.0', port=8000)"] 73 | ``` 74 | 75 | ## Build and upload the image 76 | 77 | The code to store our image on Azure Container Registry is as follows. If you are running this code, you should substitute the values of `tenant`, `app` and/or `secret` from your Azure service principal. Similarly, if you are using the public Azure cloud, note that all ACR instances share a common DNS namespace, as do all ACI and AKS instances. 78 | 79 | For more information on how to create a service principal, see the [AzureRMR readme](https://github.com/cloudyr/AzureRMR). 80 | 81 | ```r 82 | library(AzureContainers) 83 | 84 | # create a resource group for our deployments 85 | deployresgrp <- AzureRMR::get_azure_login()$ 86 | get_subscription("sub_id")$ 87 | create_resource_group("deployresgrp", location="australiaeast") 88 | 89 | # create container registry 90 | deployreg_svc <- deployresgrp$create_acr("deployreg") 91 | 92 | # build image 'bos_rf' 93 | call_docker("build -t bos_rf .") 94 | 95 | # upload the image to Azure 96 | deployreg <- deployreg_svc$get_docker_registry(as_admin=TRUE) 97 | deployreg$push("bos_rf") 98 | ``` 99 | 100 | If you run this code, you should see a lot of output indicating that R is downloading, compiling and installing randomForest, and finally that the image is being pushed to Azure. (You will see this output even if your machine already has the randomForest package installed. This is because the package is being installed to the R session _inside the container_, which is distinct from the one running the code shown here.) 101 | 102 | All Docker calls in AzureContainers, like the one to build the image, return the actual docker commandline as the `cmdline` attribute of the (invisible) returned value. In this case, the commandline is `docker build -t bos_rf .` Similarly, the `push()` method actually involves two Docker calls, one to retag the image, and the second to do the actual pushing; the returned value in this case will be a 2-component list with the command lines being `docker tag bos_rf deployreg.azurecr.io/bos_rf` and `docker push deployreg.azurecr.io/bos_rf`. 103 | 104 | 105 | ## Deploy to an Azure Container Instance 106 | 107 | The simplest way to deploy a service is via a Container Instance. The following code creates a single running container which contains our model, listening on port 8000. 108 | 109 | ```r 110 | # create an instance with 2 cores and 8GB memory, and deploy our image 111 | deployaci <- deployresgrp$create_aci("deployaci", 112 | image="deployreg.azurecr.io/bos_rf", 113 | registry_creds=deployreg, 114 | cores=2, memory=8, 115 | ports=aci_ports(8000)) 116 | ``` 117 | 118 | Once the instance is running, let's call the prediction API with some sample data. By default, AzureContainers will assign the container a domain name with prefix taken from the instance name. The port is 8000 as specified in the Dockerfile, and the URI path is `/score` indicating we want to call the scoring function defined earlier. 119 | 120 | The data to be scored---the first 10 rows of the Boston dataset---is passed in the _body_ of the request as a named list, encoded as JSON. A feature of Plumber is that, when the body of the request is in this format, it will extract the elements of the list and pass them to the scoring function as named arguments. This makes it easy to pass around relatively large amounts of data, eg if the data is wide, or for scoring multiple rows at a time. For more information on how to create and interact with Plumber APIs, consult the [Plumber documentation](https://www.rplumber.io/docs/). 121 | 122 | ```r 123 | response <- httr::POST("http://deployaci.australiaeast.azurecontainer.io:8000/score", 124 | body=list(df=MASS::Boston[1:10,]), encode="json") 125 | httr::content(response, simplifyVector=TRUE) 126 | #> [1] 25.9269 22.0636 34.1876 33.7737 34.8081 27.6394 21.8007 22.3577 16.7812 18.9785 127 | ``` 128 | 129 | 130 | ## Deploy to a Kubernetes cluster 131 | 132 | Deploying a service to a container instance is simple, but lacks many features that are important in a production setting. A better alternative for production purposes is to deploy to a Kubernetes cluster. Such a cluster can be created using Azure Kubernetes Service (AKS). 133 | 134 | ```r 135 | # create a Kubernetes cluster with 2 nodes, running Linux (the default) 136 | deployclus_svc <- deployresgrp$create_aks("deployclus", agent_pools=agent_pool("pool1", 2)) 137 | ``` 138 | 139 | Unlike an ACI resource, creating a Kubernetes cluster can take several minutes. By default, the `create_aks()` method will wait until the cluster provisioning is complete before it returns. 140 | 141 | Having created the cluster, we can deploy our model and create a service. We'll use a YAML configuration file to specify the details for the deployment and service API. The image to be deployed is the same as before. 142 | 143 | ```yaml 144 | apiVersion: apps/v1 145 | kind: Deployment 146 | metadata: 147 | name: bos-rf 148 | spec: 149 | selector: 150 | matchLabels: 151 | app: bos-rf 152 | replicas: 1 153 | template: 154 | metadata: 155 | labels: 156 | app: bos-rf 157 | spec: 158 | containers: 159 | - name: bos-rf 160 | image: deployreg.azurecr.io/bos_rf 161 | ports: 162 | - containerPort: 8000 163 | resources: 164 | requests: 165 | cpu: 250m 166 | limits: 167 | cpu: 500m 168 | imagePullSecrets: 169 | - name: deployreg.azurecr.io 170 | --- 171 | apiVersion: v1 172 | kind: Service 173 | metadata: 174 | name: bos-rf-svc 175 | spec: 176 | selector: 177 | app: bos-rf 178 | type: LoadBalancer 179 | ports: 180 | - protocol: TCP 181 | port: 8000 182 | ``` 183 | 184 | The following code will obtain the cluster endpoint from the AKS resource and then deploy the image and service to the cluster. The configuration details for the `deployclus` cluster are stored in a file located in the R temporary directory; all of the cluster's methods will use this file. Unless told otherwise, AzureContainers does not touch your default Kubernetes configuration (`~/kube/config`). 185 | 186 | ```r 187 | # grant the cluster pull access to the registry 188 | deployreg_svc$add_role_assignment(deployclus_svc, "Acrpull") 189 | 190 | # get the cluster endpoint 191 | deployclus <- deployclus_svc$get_cluster() 192 | 193 | # create and start the service 194 | deployclus$create("bos_rf.yaml") 195 | ``` 196 | 197 | To check on the progress of the deployment, run the `get()` methods specifying the type and name of the resource to get information on. As with Docker, these correspond to calls to the `kubectl` commandline tool, and again, the actual commandline is stored as the `cmdline` attribute of the returned value. 198 | 199 | ```r 200 | deployclus$get("deployment bos-rf") 201 | #> Kubernetes operation: get deployment bos-rf --kubeconfig=".../kubeconfigxxxx" 202 | #> NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE 203 | #> bos-rf 1 1 1 1 5m 204 | 205 | svc <- read.table(text=deployclus$get("service bos-rf-svc")$stdout, header=TRUE) 206 | #> Kubernetes operation: get service bos-rf-svc --kubeconfig=".../kubeconfigxxxx" 207 | #> NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 208 | #> bos-rf-svc LoadBalancer 10.0.8.189 52.187.249.58 8000:32276/TCP 5m 209 | ``` 210 | 211 | Once the service is up and running, as indicated by the presence of an external IP in the service details, let's test it with a HTTP request. The response should be the same as it was with the container instance. Notice how we extract the IP address from the service details above. 212 | 213 | ```r 214 | response <- httr::POST(paste0("http://", svc$EXTERNAL.IP[1], ":8000/score"), 215 | body=list(df=MASS::Boston[1:10, ]), encode="json") 216 | httr::content(response, simplifyVector=TRUE) 217 | #> [1] 25.9269 22.0636 34.1876 33.7737 34.8081 27.6394 21.8007 22.3577 16.7812 18.9785 218 | ``` 219 | 220 | Finally, once we are done, we can tear down the service and deployment. Depending on the version of Kubernetes the cluster is running, deleting the service may take a few minutes. 221 | 222 | ```r 223 | deployclus$delete("service", "bos-rf-svc") 224 | deployclus$delete("deployment", "bos-rf") 225 | ``` 226 | 227 | And if required, we can also delete all the resources created here, by simply deleting the resource group (AzureContainers will prompt you for confirmation): 228 | 229 | ```r 230 | deployresgrp$delete() 231 | ``` 232 | 233 | ### Security note 234 | 235 | One important thing to note about the above example is that it is **insecure**. The Plumber service is exposed over HTTP, and there is no authentication layer: anyone on the Internet can contact the service and interact with it. Therefore, it's highly recommended that you should provide at least some level of authentication, as well as restricting the service to HTTPS only (this will require deploying an ingress controller to the Kubernetes cluster). You can also create the AKS resource as a private cluster; however, be aware that if you do this, you can only interact with the cluster endpoint from a host which is on the cluster's own subnet. 236 | 237 | 238 | ## See also 239 | 240 | Plumber is a relatively simple framework for creating and deploying services. As an alternative, the [RestRserve](https://restrserve.org) package is a more comprehensive framework, built on top of functionality provided by Rserve. It includes features such as automatic parallelisation, support for HTTPS, and support for basic and bearer authentication schemes. See the vignette "Deploying an ACI service with HTTPS and authentication" for more information. 241 | -------------------------------------------------------------------------------- /vignettes/vig02_restrserve_deploy_aci.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Deploying an ACI service with HTTPS and authentication" 3 | Author: Hong Ooi 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{RestRserve model deployment to ACI} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{utf8} 9 | --- 10 | 11 | This document shows how you can deploy a fitted model as a web service to an Azure Container Instance, using the [RestRserve](https://restrserve.org) package. RestRserve has a number of features that can make it more suitable than Plumber for building robust, production-ready services. These include: 12 | 13 | - Automatic parallelisation, based on the Rserve backend 14 | - Support for HTTPS 15 | - Support for basic and bearer HTTP authentication schemes 16 | 17 | In particular, we'll show how to implement the latter two features in this vignette. 18 | 19 | ## Deployment artifacts 20 | 21 | ### Model object 22 | 23 | For illustrative purposes, we'll reuse the random forest model and resource group from the Plumber deployment vignette. The code to fit the model is reproduced below for convenience. 24 | 25 | ```r 26 | data(Boston, package="MASS") 27 | library(randomForest) 28 | 29 | # train a model for median house price as a function of the other variables 30 | bos_rf <- randomForest(medv ~ ., data=Boston, ntree=100) 31 | 32 | # save the model 33 | saveRDS(bos_rf, "bos_rf.rds") 34 | ``` 35 | 36 | Basic authentication requires that we provide a list of usernames and passwords that grant access to the service. In a production setting, you would typically query a database, directory service or other backing store to authenticate users. To keep this example simple, we'll just create a flat file in the standard [Apache `.htpasswd` format](https://en.wikipedia.org/wiki/.htpasswd). In this format, the passwords are encrypted using a variety of algorithms, as a security measure; we'll use the bcrypt algorithm since an R implementation is available in the package of that name. 37 | 38 | ```r 39 | library(bcrypt) 40 | 41 | user_list <- list( 42 | c("user1", "password1"), 43 | c("user2", "password2") 44 | ) 45 | user_str <- sapply(user_list, function(x) paste(x[1], hashpw(x[2]), sep=":")) 46 | writeLines(user_str, ".htpasswd") 47 | ``` 48 | 49 | ### TLS certificate/private key 50 | 51 | To enable HTTPS, we need to provide a TLS certificate and private key. Again, in a production setting, the cert will typically be provided to you; for this vignette, we'll generate a self-signed cert instead. If you are running Linux or MacOS and have openssl installed, you can use that to generate the cert. Here, since we're already using Azure, we'll leverage the Azure Key Vault service to do it in a platform-independent manner. 52 | 53 | ```r 54 | library(AzureRMR) 55 | library(AzureContainers) 56 | library(AzureKeyVault) 57 | 58 | deployresgrp <- AzureRMR::get_azure_login()$ 59 | get_subscription("sub_id")$ 60 | get_resource_group("deployresgrp") 61 | 62 | # create the key vault 63 | vault_res <- deployresgrp$create_key_vault("mykeyvault") 64 | 65 | # get the vault endpoint 66 | kv <- vault_res$get_endpoint() 67 | 68 | # generate the certificate: use the DNS name of the ACI container endpoint 69 | kv$certificates$create( 70 | "deployrrsaci", 71 | "CN=deployrrsaci", 72 | x509=cert_x509_properties(dns_names=c("deployrrsaci.australiaeast.azurecontainer.io")) 73 | ) 74 | secret <- kv$secrets$get("deployrrsaci") 75 | key <- sub("-----BEGIN CERTIFICATE-----.*$", "", secret$value) 76 | cer <- sub("^.*-----END PRIVATE KEY-----\n", "", secret$value) 77 | writeLines(key, "cert.key") 78 | writeLines(cer, "cert.cer") 79 | ``` 80 | 81 | ### App 82 | 83 | Unlike Plumber, in RestRserve you define your service in R code, as a web app. An app is an object of R6 class `Application`: it contains various middleware and backend objects, and exposes the endpoint paths for your service. The overall server backend is of R6 class `BackendRserve`, and has responsibility for running and managing the app. 84 | 85 | The script below defines an app that exposes the scoring function on the `/score` path. Save this as `app.R`: 86 | 87 | ```r 88 | library(RestRserve) 89 | library(randomForest) 90 | 91 | bos_rf <- readRDS("bos_rf.rds") 92 | 93 | users <- local({ 94 | usr <- read.table(".htpasswd", sep=":", stringsAsFactors=FALSE) 95 | structure(usr[[2]], names=usr[[1]]) 96 | }) 97 | 98 | # scoring function: calls predict() on the provided dataset 99 | # - input is a jsonified data frame, in the body of a POST request 100 | # - output is the predicted values 101 | score <- function(request, response) 102 | { 103 | df <- jsonlite::fromJSON(rawToChar(request$body), simplifyDataFrame=TRUE) 104 | sc <- predict(bos_rf, df) 105 | 106 | response$set_body(jsonlite::toJSON(sc, auto_unbox=TRUE)) 107 | response$set_content_type("application/json") 108 | } 109 | 110 | # basic authentication against provided username/password values 111 | # use try() construct to ensure robustness against malicious input 112 | authenticate <- function(user, password) 113 | { 114 | res <- FALSE 115 | try({ 116 | res <- bcrypt::checkpw(password, users[[user]]) 117 | }, silent=TRUE) 118 | res 119 | } 120 | 121 | # chain of objects for app 122 | auth_backend <- AuthBackendBasic$new(FUN=authenticate) 123 | auth_mw <- AuthMiddleware$new(auth_backend=auth_backend, routes="/score") 124 | app <- Application$new(middleware=list(auth_mw)) 125 | app$add_post(path="/score", FUN=score) 126 | 127 | backend <- BackendRserve$new(app) 128 | ``` 129 | 130 | ### Dockerfile 131 | 132 | Here is the dockerfile for the image. Save this as `RestRserve-aci.dockerfile`: 133 | 134 | ```dockerfile 135 | FROM rexyai/restrserve 136 | 137 | # install required packages 138 | RUN Rscript -e "install.packages(c('randomForest', 'bcrypt'), repos='https://cloud.r-project.org')" 139 | 140 | # copy model object, cert files, user file and app script 141 | RUN mkdir /data 142 | COPY bos_rf.rds /data 143 | COPY .htpasswd /data 144 | COPY cert.cer /data 145 | COPY cert.key /data 146 | COPY app.R /data 147 | 148 | WORKDIR /data 149 | 150 | EXPOSE 8080 151 | 152 | CMD ["Rscript", "-e", "source('app.R'); backend$start(app, http_port=-1, https.port=8080, tls.key=normalizePath('cert.key'), tls.cert=normalizePath('cert.cer'))"] 153 | ``` 154 | 155 | ## Create the container 156 | 157 | We now build the image and upload it to an Azure Container Registry. This assumes a fresh start; if you have created an ACR in this resource group already, you can reuse that instead by calling `get_acr` instead of `create_acr`. 158 | 159 | ```r 160 | call_docker("build -t rrs-aci -f RestRserve-aci.dockerfile .") 161 | 162 | deployreg_svc <- deployresgrp$create_acr("deployreg") 163 | deployreg <- deployreg_svc$get_docker_registry(as_admin=TRUE) 164 | deployreg$push("rrs-aci") 165 | ``` 166 | 167 | We can now deploy the image to ACI and obtain predicted values from the RestRserve app. Because we used a self-signed certificate in this example, we need to turn off the SSL verification check that curl performs by default. There may also be a short delay from when the container is started, to when the app is ready to accept requests. 168 | 169 | ```r 170 | # ensure the name of the resource matches the one on the cert we obtained above 171 | deployresgrp$create_aci("deployrrsaci", 172 | image="deployreg.azurecr.io/bos-rrs-https", 173 | registry_creds=deployreg, 174 | cores=2, memory=8, 175 | ports=aci_ports(8080)) 176 | 177 | Sys.sleep(30) 178 | 179 | # tell curl not to verify the cert 180 | unverified_handle <- function() 181 | { 182 | structure(list( 183 | handle=curl::handle_setopt(curl::new_handle(), ssl_verifypeer=FALSE), 184 | url="https://deployrrsaci.australiaeast.azurecontainer.io"), 185 | class="handle") 186 | } 187 | 188 | # send the username and password as part of the request 189 | response <- httr::POST("https://deployrrsaci.australiaeast.azurecontainer.io:8080/score", 190 | httr::authenticate("user1", "password1"), 191 | body=MASS::Boston[1:10, ], encode="json", 192 | handle=unverified_handle()) 193 | 194 | httr::content(response, simplifyVector=TRUE) 195 | #> [1] 25.9269 22.0636 34.1876 33.7737 34.8081 27.6394 21.8007 22.3577 16.7812 18.9785 196 | ``` 197 | -------------------------------------------------------------------------------- /vignettes/vig03_securing_aks_traefik.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Securing an AKS deployment with Traefik" 3 | Author: Hong Ooi 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Securing an AKS deployment with Traefik} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{utf8} 9 | --- 10 | 11 | The vignette "Deploying a prediction service with Plumber" showed you how to deploy a simple prediction service to a Kubernetes cluster in Azure. That deployment had a number of drawbacks: chiefly, it was open to the public, and used HTTP and thus was vulnerable to man-in-the-middle (MITM) attacks. Here, we'll show how to address these issues with basic authentication and HTTPS. To run the code in this vignette, you'll need the helm binary installed on your machine in addition to docker and kubectl. 12 | 13 | ## Model object and image 14 | 15 | We'll reuse the image from the Plumber vignette. The code and artifacts to build this image are reproduced below for convenience. 16 | 17 | Save the model object: 18 | 19 | ```r 20 | data(Boston, package="MASS") 21 | library(randomForest) 22 | 23 | # train a model for median house price as a function of the other variables 24 | bos_rf <- randomForest(medv ~ ., data=Boston, ntree=100) 25 | 26 | # save the model 27 | saveRDS(bos_rf, "bos_rf.rds") 28 | ``` 29 | 30 | Scoring script: 31 | 32 | ```r 33 | bos_rf <- readRDS("bos_rf.rds") 34 | library(randomForest) 35 | 36 | #* @param df data frame of variables 37 | #* @post /score 38 | function(req, df) 39 | { 40 | df <- as.data.frame(df) 41 | predict(bos_rf, df) 42 | } 43 | ``` 44 | 45 | Dockerfile: 46 | 47 | ```dockerfile 48 | FROM trestletech/plumber 49 | 50 | # install the randomForest package 51 | RUN R -e 'install.packages(c("randomForest"))' 52 | 53 | # copy model and scoring script 54 | RUN mkdir /data 55 | COPY bos_rf.rds /data 56 | COPY bos_rf_score.R /data 57 | WORKDIR /data 58 | 59 | # plumb and run server 60 | EXPOSE 8000 61 | ENTRYPOINT ["R", "-e", \ 62 | "pr <- plumber::plumb('/data/bos_rf_score.R'); pr$run(host='0.0.0.0', port=8000)"] 63 | ``` 64 | 65 | Build and upload the image to ACR: 66 | 67 | ```r 68 | library(AzureContainers) 69 | 70 | # create a resource group for our deployments 71 | deployresgrp <- AzureRMR::get_azure_login()$ 72 | get_subscription("sub_id")$ 73 | create_resource_group("deployresgrp", location="australiaeast") 74 | 75 | # create container registry 76 | deployreg_svc <- deployresgrp$create_acr("deployreg") 77 | 78 | # build image 'bos_rf' 79 | call_docker("build -t bos_rf .") 80 | 81 | # upload the image to Azure 82 | deployreg <- deployreg_svc$get_docker_registry() 83 | deployreg$push("bos_rf") 84 | ``` 85 | 86 | ## Deploy to AKS 87 | 88 | Create a new AKS resource for this deployment: 89 | 90 | ```r 91 | # create a Kubernetes cluster 92 | deployclus_svc <- deployresgrp$create_aks("secureclus", agent_pools=agent_pool("pool1", 2)) 93 | 94 | # grant the cluster pull access to the registry 95 | deployreg_svc$add_role_assignment(deployclus_svc, "Acrpull") 96 | ``` 97 | 98 | ### Install traefik 99 | 100 | The middleware we'll use to enable HTTPS and authentication is [Traefik](https://traefik.io/traefik/). This features built-in integration with Let's Encrypt, which is a popular source for TLS certificates (necessary for HTTPS). An alternative to Traefik is [Nginx](https://www.f5.com/go/product/welcome-to-nginx). 101 | 102 | Deploying traefik and enabling HTTPS involves the following steps, on top of deploying the model service itself: 103 | 104 | - Install traefik 105 | - Assign a domain name to the cluster's public IP address 106 | - Deploy the ingress route 107 | 108 | The following yaml contains the configuration parameters for traefik. Insert your email address where indicated (it will be used to obtain a certificate from Let's Encrypt), and save this as `traefik_values.yaml`. 109 | 110 | ```yaml 111 | # traefik_values.yaml 112 | fullnameOverride: traefik 113 | replicas: 1 114 | resources: 115 | limits: 116 | cpu: 500m 117 | memory: 500Mi 118 | requests: 119 | cpu: 100m 120 | memory: 200Mi 121 | externalTrafficPolicy: Local 122 | kubernetes: 123 | ingressClass: traefik 124 | ingressEndpoint: 125 | useDefaultPublishedService: true 126 | dashboard: 127 | enabled: false 128 | debug: 129 | enabled: false 130 | accessLogs: 131 | enabled: true 132 | fields: 133 | defaultMode: keep 134 | headers: 135 | defaultMode: keep 136 | names: 137 | Authorization: redact 138 | rbac: 139 | enabled: true 140 | acme: 141 | enabled: true 142 | email: "email.addr@example.com" # insert your email address here 143 | staging: false 144 | challengeType: tls-alpn-01 145 | ssl: 146 | enabled: true 147 | enforced: true 148 | permanentRedirect: true 149 | tlsMinVersion: VersionTLS12 150 | cipherSuites: 151 | - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 152 | - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 153 | - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 154 | - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 155 | - TLS_RSA_WITH_AES_128_GCM_SHA256 156 | - TLS_RSA_WITH_AES_256_GCM_SHA384 157 | - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 158 | ``` 159 | 160 | We can now install traefik: 161 | 162 | ```r 163 | # get the cluster endpoint 164 | deployclus <- deployclus_svc$get_cluster() 165 | 166 | deployclus$helm("repo add stable https://kubernetes-charts.storage.googleapis.com/") 167 | deployclus$helm("repo update") 168 | deployclus$helm("install traefik-ingress stable/traefik --namespace kube-system --values traefik_values.yaml") 169 | ``` 170 | 171 | ### Assign domain name 172 | 173 | Installing an ingress controller will create a public IP resource, which is the visible address for the cluster. It takes a minute or two for this resource to be created; once it appears, we assign a domain name to it. 174 | 175 | ```r 176 | for(i in 1:100) 177 | { 178 | res <- read.table(text=deployclus$get("service", "--all-namespaces")$stdout, header=TRUE, stringsAsFactors=FALSE) 179 | ip <- res$EXTERNAL.IP[res$NAME == "traefik"] 180 | if(ip != "") 181 | break 182 | Sys.sleep(10) 183 | } 184 | if(ip == "") stop("Ingress public IP not assigned") 185 | 186 | cluster_resources <- deployclus_svc$list_cluster_resources() 187 | found <- FALSE 188 | for(res in cluster_resources) 189 | { 190 | if(res$type != "Microsoft.Network/publicIPAddresses") 191 | next 192 | res$sync_fields() 193 | if(res$properties$ipAddress == ip) 194 | { 195 | found <- TRUE 196 | break 197 | } 198 | } 199 | if(!found) stop("Ingress public IP resource not found") 200 | 201 | # assign domain name to IP address 202 | res$do_operation( 203 | body=list( 204 | location=ip$location, 205 | properties=list( 206 | dnsSettings=list(domainNameLabel="secureclus"), 207 | publicIPAllocationMethod=ip$properties$publicIPAllocationMethod 208 | ) 209 | ), 210 | http_verb="PUT" 211 | ) 212 | ``` 213 | 214 | ### Generate an auth file 215 | 216 | We'll use Traefik to handle authentication details. While some packages like RestRserve can also authenticate users, a key benefit of assigning this to the ingress controller is that it frees our container to focus purely on the task of returning predicted values. For example, if we want to switch to a different kind of model, we can do so without having to copy over any authentication code. 217 | 218 | Using basic auth with Traefik requires an authentication file, in [Apache `.htpasswd` format](https://en.wikipedia.org/wiki/.htpasswd). Here is R code to create such a file. This is essentially the same code that is used in the "Deploying an ACI service with HTTPS and authentication" vignette. 219 | 220 | ```r 221 | library(bcrypt) 222 | 223 | user_list <- list( 224 | c("user1", "password1") 225 | ) 226 | user_str <- sapply(user_list, function(x) paste(x[1], hashpw(x[2]), sep=":")) 227 | writeLines(user_str, "auth") 228 | ``` 229 | 230 | ### Deploy the service 231 | 232 | The yaml for the deployment and service is the same as in the original Plumber vignette. The main difference is that we now create a separate `bos-rf` namespace for our objects, which is the recommended practice: it allows us to manage the service independently of any others that may exist on the cluster. 233 | 234 | Save the following as `secure_deploy.yaml`: 235 | 236 | ```yaml 237 | # deployment.yaml 238 | apiVersion: apps/v1 239 | kind: Deployment 240 | metadata: 241 | name: bos-rf 242 | namespace: bos-rf 243 | spec: 244 | selector: 245 | matchLabels: 246 | app: bos-rf 247 | replicas: 1 248 | template: 249 | metadata: 250 | labels: 251 | app: bos-rf 252 | spec: 253 | containers: 254 | - name: bos-rf 255 | image: deployreg.azurecr.io/bos_rf # insert URI for your container registry/image 256 | ports: 257 | - containerPort: 8000 258 | resources: 259 | requests: 260 | cpu: 250m 261 | limits: 262 | cpu: 500m 263 | --- 264 | apiVersion: v1 265 | kind: Service 266 | metadata: 267 | name: bos-rf-svc 268 | namespace: bos-rf 269 | spec: 270 | selector: 271 | app: bos-rf 272 | ports: 273 | - protocol: TCP 274 | port: 8000 275 | ``` 276 | 277 | The yaml to create the ingress route is below. Save this as `secure_ingress.yaml`. 278 | 279 | ```yaml 280 | # ingress.yaml 281 | apiVersion: extensions/v1beta1 282 | kind: Ingress 283 | metadata: 284 | name: bos-rf-ingress 285 | namespace: bos-rf 286 | annotations: 287 | kubernetes.io/ingress.class: traefik 288 | ingress.kubernetes.io/auth-type: basic 289 | ingress.kubernetes.io/auth-secret: bos-rf-secret 290 | spec: 291 | rules: 292 | - host: secureclus.australiaeast.cloudapp.azure.com 293 | http: 294 | paths: 295 | - path: / 296 | backend: 297 | serviceName: bos-rf-svc 298 | servicePort: 8000 299 | ``` 300 | 301 | We can now create the deployment, service, and ingress route: 302 | 303 | ```r 304 | deployclus$kubectl("create namespace bos-rf") 305 | deployclus$kubectl("create secret generic bos-rf-secret --from-file=auth --namespace bos-rf") 306 | deployclus$create("secure_deploy.yaml") 307 | deployclus$apply("secure_ingress.yaml") 308 | ``` 309 | 310 | ## Call the service 311 | 312 | Once the deployment is complete, we can obtain predicted values from it. Notice that we use the default HTTPS port for the request (port 443). While port 8000 is nominally exposed by the service, this is visible only within the cluster; it is connected to the external port 443 by Traefik. 313 | 314 | ```r 315 | response <- httr::POST("https://secureclus.australiaeast.cloudapp.azure.com/score", 316 | httr::authenticate("user1", "password1"), 317 | body=list(df=MASS::Boston[1:10,]), encode="json") 318 | httr::content(response, simplifyVector=TRUE) 319 | #> [1] 25.9269 22.0636 34.1876 33.7737 34.8081 27.6394 21.8007 22.3577 16.7812 18.9785 320 | ``` 321 | 322 | ## Further comments 323 | 324 | In this vignette, we've secured the predictive service with a single username and password. While we could add more users to the authentication file, a more flexible and scalable solution is to use a frontend service, such as [Azure API Management](https://azure.microsoft.com/products/api-management/), or a directory service like [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) (previously known as Azure Active Directory). The specifics will typically be determined by your organisation's IT infrastructure, and are beyond the scope of this vignette. 325 | 326 | -------------------------------------------------------------------------------- /vignettes/vig04_rbac.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "RBAC examples" 3 | Author: Hong Ooi 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{RBAC examples} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{utf8} 9 | --- 10 | 11 | Working with role-based access control (RBAC) can be tricky, especially when containers are involved. This vignette provides example code snippets to handle some common use cases. 12 | 13 | ## Authenticating with a service principal from ACI to ACR 14 | 15 | This covers the scenario where you want to deploy an image to a container instance using a service principal, rather than the registry's admin credentials. 16 | 17 | ```r 18 | library(AzureGraph) 19 | az <- AzureRMR::get_azure_login() 20 | gr <- AzureGraph::get_graph_login() 21 | 22 | # create the registry 23 | rg <- az$ 24 | get_subscription("sub_id")$ 25 | get_resource_group("rgname") 26 | 27 | acr <- rg$create_acr("myacr") 28 | 29 | # create an app and give it pull access to the registry 30 | app <- gr$create_app("mycontainerapp") 31 | acr$add_role_assignment(app, "Acrpull") 32 | 33 | # build and push an image 34 | call_docker("build -t myimage .") 35 | reg <- acr$get_docker_registry() 36 | reg$push("myimage") 37 | 38 | # create an ACI credentials object containing the app ID and password 39 | creds <- aci_creds("myacr.azurecr.io", username=app$properties$appId, password=app$password) 40 | 41 | # create the instance, passing it the credentials object 42 | rg$create_aci("myinstance", image="myacr.azurecr.io/myimage", 43 | registry_creds=creds) 44 | ``` 45 | 46 | ## Authenticating with a service principal from AKS to ACR 47 | 48 | The corresponding scenario for a Kubernetes cluster is much simpler: we simply call the `add_role_assignment` method for the ACR object, passing it the AKS object. We'll reuse the registry from the above example. 49 | 50 | ```r 51 | # create the AKS resource 52 | aks <- rg$create_aks("myaks", agent_pools=aks_pools("pool1", 2), enable_rbac=TRUE) 53 | 54 | # give the cluster pull access to the registry 55 | reg$add_role_assignment(aks, "Acrpull") 56 | ``` 57 | 58 | After giving the cluster the necessary permissions, you can then deploy images from the registry as normal. 59 | 60 | ## Creating an AKS resource and reusing an existing service principal 61 | 62 | This scenario is most relevant when creating an AKS resource in an automated environment, ie without a logged-in user's credentials. Currently, creating an AKS resource also involves creating an associated service principal, for the cluster to manage its sub-resources. In turn, creating this service principal will attempt to get the credentials for the logged-in user, which fails if there is no user present. 63 | 64 | To avoid this, you can create an app ahead of time and pass it to `create_aks`: 65 | 66 | ```r 67 | # login to ARM and MS Graph with client credentials flow 68 | # your app must have the right permissions to work with ARM and Graph 69 | az <- AzureRMR::create_azure_login("mytenant", app="app_id", password="clientsecret") 70 | gr <- AzureGraph::create_graph_login("mytenant", app="app_id", password="clientsecret") 71 | 72 | app <- gr$create_app("myaksapp") 73 | 74 | az$get_subscription("sub_id")$ 75 | get_resource_group("rgname")$ 76 | create_aks("myaks", 77 | cluster_service_principal=app, 78 | agent_pools=aks_pools("pool1", 2, "Standard_DS2_v2", "Linux")) 79 | ``` 80 | 81 | ## Integrating AKS with Azure Active Directory 82 | 83 | Integrating AKS and AAD requires creating two registered apps, the client and server, and giving them permissions to talk to each other. Most of the work here is actually done using the AzureGraph package; once the apps are correctly configured, we then pass them to the `create_aks` method. You'll need to be an administrator for your AAD tenant to carry out these steps. 84 | 85 | ```r 86 | # create the server app 87 | srvapp <- gr$create_app("akssrvapp") 88 | 89 | # save the app ID and password 90 | srvapp_id <- srvapp$properties$appId 91 | srvapp_pwd <- srvapp$password 92 | 93 | # update group membership claims 94 | srvapp$update(groupMembershipClaims="all") 95 | 96 | # update API permissions (Directory.Read.All scope & role, User.Read.All scope) 97 | srvapp$update(requiredResourceAccess=list( 98 | list( 99 | resourceAppId="00000003-0000-0000-c000-000000000000", 100 | resourceAccess=list( 101 | list(id="06da0dbc-49e2-44d2-8312-53f166ab848a", type="Scope"), 102 | list(id="e1fe6dd8-ba31-4d61-89e7-88639da4683d", type="Scope"), 103 | list(id="7ab1d382-f21e-4acd-a863-ba3e13f7da61", type="Role") 104 | ) 105 | ) 106 | )) 107 | 108 | # add OAuth permissions API 109 | srvapp_api <- srvapp$properties$api 110 | srvapp_newapi_id <- uuid::UUIDgenerate() 111 | srvapp_api$oauth2PermissionScopes <- list( 112 | list( 113 | adminConsentDescription="AKS", 114 | adminConsentDisplayName="AKS", 115 | id=srvapp_newapi_id, 116 | isEnabled=TRUE, 117 | type="User", 118 | userConsentDescription="AKS", 119 | userConsentDisplayName="AKS", 120 | value="AKS" 121 | ) 122 | ) 123 | srvapp$update(api=srvapp_api, identifierUris=I(sprintf("api://%s", srvapp_id))) 124 | 125 | # create the client app 126 | cliapp <- gr$create_app("akscliapp", 127 | isFallbackPublicClient=TRUE, 128 | publicClient=list(redirectUris=list("https://akscliapp")) 129 | ) 130 | cliapp_id <- cliapp$properties$appId 131 | 132 | # tell the server app to trust the client 133 | srvapp_api <- srvapp$properties$api 134 | srvapp_api$preAuthorizedApplications <- list( 135 | list( 136 | appId=cliapp_id, 137 | permissionIds=list(srvapp_newapi_id) 138 | ) 139 | ) 140 | srvapp$update(api=srvapp_api) 141 | ``` 142 | 143 | Once the apps have been configured, we still have to grant admin consent. This is best done in the Azure Portal: 144 | 145 | - Click on "Azure Active Directory" in the list of items on the left 146 | - In the AAD blade, click on "App registrations" 147 | - Find the app that you created, and click on it. 148 | - Click on "API permissions". 149 | - Click on the "Grant admin consent" button at the bottom of the pane. 150 | 151 | ![](perms2.png "permissions") 152 | 153 | Having created and configured the apps, we can then create the cluster resource. 154 | 155 | ```r 156 | rg$create_aks("akswithaad", agent_pools=aks_pools("pool1", 2), 157 | properties=list( 158 | aadProfile=list( 159 | clientAppID=cliapp_id, 160 | serverAppID=srvapp_id, 161 | serverAppSecret=srvapp_pwd 162 | ) 163 | ), 164 | enable_rbac=TRUE 165 | ) 166 | ``` 167 | 168 | For more information, see the following Microsoft Docs pages: 169 | 170 | - [Enable Azure Active Directory integration](https://learn.microsoft.com/en-us/azure/aks/azure-ad-integration) 171 | - [Use Kubernetes RBAC with Azure AD integration](https://learn.microsoft.com/en-us/azure/aks/azure-ad-rbac) 172 | --------------------------------------------------------------------------------