From df9ad284c293d6162d487270454f7bdd82d9e69e Mon Sep 17 00:00:00 2001 From: Kyle Dodson Date: Thu, 5 Sep 2024 15:36:42 -0700 Subject: [PATCH] Add liblab project --- .devcontainer/devcontainer.json | 28 ++ .editorconfig | 45 ++ .github/CODE_OF_CONDUCT.md | 75 ++++ .github/CONTRIBUTING.md | 39 ++ .github/ISSUE_TEMPLATE/bug_report.yml | 40 ++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 26 ++ .github/PULL_REQUEST_TEMPLATE.md | 8 + .github/SECURITY.md | 14 + .github/SUPPORT.md | 3 + .github/workflows/publish.yml | 32 ++ .github/workflows/stale.yml | 41 ++ .manifest.json | 234 ++++++++++ .prettierrc.yaml | 11 + .vscode/extensions.json | 12 + .vscode/settings.json | 26 ++ Example/.gitignore | 398 ++++++++++++++++++ Example/Example.csproj | 15 + Example/Program.cs | 11 + LICENSE | 21 + README.md | 93 +++- Salad.Cloud.IMDS.SDK.sln | 37 ++ Salad.Cloud.IMDS.SDK/.gitignore | 398 ++++++++++++++++++ .../Config/SaladCloudImdsSdkConfig.cs | 11 + Salad.Cloud.IMDS.SDK/Hooks/CustomHook.cs | 20 + Salad.Cloud.IMDS.SDK/Hooks/IHook.cs | 8 + Salad.Cloud.IMDS.SDK/Http/Environment.cs | 16 + .../Http/Handlers/HookHandler.cs | 33 ++ .../Http/Handlers/RetryHandler.cs | 55 +++ Salad.Cloud.IMDS.SDK/Http/RequestBuilder.cs | 152 +++++++ .../Serialization/PathSerializationStyle.cs | 8 + .../Serialization/QuerySerializationStyle.cs | 9 + .../Http/Serialization/SerializationStyle.cs | 12 + .../Http/Serialization/Serializer.cs | 164 ++++++++ .../Models/ContainerStatus.cs | 13 + Salad.Cloud.IMDS.SDK/Models/ContainerToken.cs | 10 + .../Models/ReallocateContainer.cs | 10 + .../Salad.Cloud.IMDS.SDK.csproj | 26 ++ .../SaladCloudImdsSdkClient.cs | 58 +++ Salad.Cloud.IMDS.SDK/Services/BaseService.cs | 15 + .../Services/MetadataService.cs | 70 +++ documentation/models/ContainerStatus.md | 10 + documentation/models/ContainerToken.md | 9 + documentation/models/ReallocateContainer.md | 9 + documentation/services/MetadataService.md | 81 ++++ documentation/snippets/v1-reallocate-post.md | 12 + documentation/snippets/v1-status-get.md | 10 + documentation/snippets/v1-token-get.md | 10 + global.json | 7 + install.sh | 3 + 50 files changed, 2452 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .editorconfig create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/SECURITY.md create mode 100644 .github/SUPPORT.md create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .manifest.json create mode 100644 .prettierrc.yaml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 Example/.gitignore create mode 100644 Example/Example.csproj create mode 100644 Example/Program.cs create mode 100644 LICENSE create mode 100644 Salad.Cloud.IMDS.SDK.sln create mode 100644 Salad.Cloud.IMDS.SDK/.gitignore create mode 100644 Salad.Cloud.IMDS.SDK/Config/SaladCloudImdsSdkConfig.cs create mode 100644 Salad.Cloud.IMDS.SDK/Hooks/CustomHook.cs create mode 100644 Salad.Cloud.IMDS.SDK/Hooks/IHook.cs create mode 100644 Salad.Cloud.IMDS.SDK/Http/Environment.cs create mode 100644 Salad.Cloud.IMDS.SDK/Http/Handlers/HookHandler.cs create mode 100644 Salad.Cloud.IMDS.SDK/Http/Handlers/RetryHandler.cs create mode 100644 Salad.Cloud.IMDS.SDK/Http/RequestBuilder.cs create mode 100644 Salad.Cloud.IMDS.SDK/Http/Serialization/PathSerializationStyle.cs create mode 100644 Salad.Cloud.IMDS.SDK/Http/Serialization/QuerySerializationStyle.cs create mode 100644 Salad.Cloud.IMDS.SDK/Http/Serialization/SerializationStyle.cs create mode 100644 Salad.Cloud.IMDS.SDK/Http/Serialization/Serializer.cs create mode 100644 Salad.Cloud.IMDS.SDK/Models/ContainerStatus.cs create mode 100644 Salad.Cloud.IMDS.SDK/Models/ContainerToken.cs create mode 100644 Salad.Cloud.IMDS.SDK/Models/ReallocateContainer.cs create mode 100644 Salad.Cloud.IMDS.SDK/Salad.Cloud.IMDS.SDK.csproj create mode 100644 Salad.Cloud.IMDS.SDK/SaladCloudImdsSdkClient.cs create mode 100644 Salad.Cloud.IMDS.SDK/Services/BaseService.cs create mode 100644 Salad.Cloud.IMDS.SDK/Services/MetadataService.cs create mode 100644 documentation/models/ContainerStatus.md create mode 100644 documentation/models/ContainerToken.md create mode 100644 documentation/models/ReallocateContainer.md create mode 100644 documentation/services/MetadataService.md create mode 100644 documentation/snippets/v1-reallocate-post.md create mode 100644 documentation/snippets/v1-status-get.md create mode 100644 documentation/snippets/v1-token-get.md create mode 100644 global.json create mode 100644 install.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..86c2610 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,28 @@ +{ + "name": "C# (.NET)", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm", + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5000, 5001], + // "portsAttributes": { + // "5001": { + // "protocol": "https" + // } + // } + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "sh install.sh", + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": ["ms-dotnettools.csdevkit"] + } + } + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..100cc3a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,45 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{bat,cmd}] +end_of_line = crlf +indent_size = 2 +max_line_length = 120 + +[*.cs] +charset = utf-8-bom +max_line_length = 120 + +[*.{csproj,props,targets}] +charset = utf-8-bom +indent_size = 2 + +[*.json] +indent_size = 2 +max_line_length = 120 + +[*.md] +indent_size = 2 + +[*.ps1] +end_of_line = lf +max_line_length = 120 + +[*.sh] +end_of_line = lf +indent_size = 2 +max_line_length = 120 + +[*.sln] +charset = utf-8-bom +indent_style = tab + +[*.{yml,yaml}] +indent_size = 2 +max_line_length = 120 diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c2c08db --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [`dev@salad.com`](mailto:dev@salad.com). All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html). Translations are available at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). + +Community Impact guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..b6b4d9b --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing + +Thanks for being here and for being awesome! 👍 + +The following sections outline the different ways to contribute to the project. + +## Discussing + +The easiest way to contribute to the project is by participating in [GitHub discussions](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/discussions). The community often chimes in with helpful advice when you have a question, and you may also find yourself providing answers and helping others. Be sure to review the [code of conduct](./CODE_OF_CONDUCT.md) before participating. + +_Please do not use GitHub issues to ask a question._ We will politely close a GitHub issue that asks a question and kindly refer you to one of the aforementioned avenues. + +## Reporting Bugs + +We're sorry if this happened to you! Consider jumping into [GitHub discussions](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/discussions) first. The community may have already found a solution. + +You can create a [GitHub issue](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/issues) to report bugs. You can also create an [official support request](mailto:cloud@salad.com) if you have a specific question that should be answered by a team member. + +## Requesting Features + +We love a good idea. Do you have one? Consider jumping into [GitHub discussions](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/discussions) first. The community may have some interesting insights. + +You can create a [GitHub issue](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/issues) to request new features. + +## Reporting Security Vulnerabilities + +We take security seriously, and we appreciate your cooperation in disclosing vulnerabilities to us responsibly. Refer to our [security policy](./SECURITY.md) for more details. + +_Please do not use public GitHub issues to report a security vulnerability._ + +## Changing Code + +Interested in changing the world? + +First, take note that the code in this project is automatically generated by [liblab](https://liblab.com/) using the official OpenAPI Specification document for the SaladCloud API. Not all code contributions are accepted as they may simply be overwritten the next time the code is automatically generated. + +Before starting on any code contribution, please discuss it with the team first to ensure it is compatible with this project's toolchain and fits in the product roadmap. We will politely close a GitHub pull request that is not compatible with or cannot be maintained by this project's toolchain. + +Additionally, please consider taking a moment to read Miguel de Icaza's blog post titled [Open Source Contribution Etiquette](https://tirania.org/blog/archive/2010/Dec-31.html) and Ilya Grigorik's blog post titled [Don't "Push" Your Pull Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/). diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..f753c86 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,40 @@ +name: Report a bug +description: Let us know if you encountered an issue. +labels: + - bug +body: + - type: markdown + attributes: + value: Thanks for taking the time to fill out this bug report! We encourage you to start with a [GitHub discussion](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/discussions) to ensure this issue is new. If this issue is security related, please disclose vulnerabilities to us responsibly as a [GitHub security advisory](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/security/advisories/new). + - type: textarea + id: actual + attributes: + label: What actually happened? + description: Please provide as much information about the issue as possible. + validations: + required: true + - type: textarea + id: expected + attributes: + label: What did you expect to happen? + description: Please provide as much information about the issue as possible. + validations: + required: true + - type: textarea + id: repro + attributes: + label: How can we reproduce it? + description: Please provide as succinct a reproduction as possible. + validations: + required: true + - type: textarea + id: extra + attributes: + label: Anything else we should know? + - type: textarea + id: sdkVersion + attributes: + label: What is the version of your SaladCloud SDK? + description: e.g. 1.0.1 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..8dab5e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Request support + url: https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/discussions + about: Please use GitHub discussions for general chat and community-provided support. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..0673691 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,26 @@ +name: Request a feature +description: Let us know if you have ideas for issues you would like to see supported. +labels: + - enhancement +body: + - type: markdown + attributes: + value: Thanks for taking the time to fill out this feature request! We encourage you to start with a [GitHub discussion](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/discussions) to ensure this issue fits in the roadmap. If this issue is security related, please disclose vulnerabilities to us responsibly as a [GitHub security advisory](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/security/advisories/new). + - type: textarea + id: feature + attributes: + label: What do you want? + description: Please provide as much information about the issue as possible. + validations: + required: true + - type: textarea + id: expected + attributes: + label: Why is it needed? + description: Please provide as much information about the issue as possible. + validations: + required: true + - type: textarea + id: extra + attributes: + label: Anything else we should know? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..bc6f0af --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ + diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..26451b5 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,14 @@ +# Security + +## Reporting Security Vulnerabilities + +We take security seriously, and we appreciate your cooperation in disclosing vulnerabilities to us responsibly. + +_Please do not use public GitHub issues to report a security vulnerability._ + +Instead, please do one of the following: + +- Open a GitHub security advisory on the [GitHub repository](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/security/advisories/new) +- Send an email to [`dev@salad.com`](mailto:dev@salad.com) + +Please include as much information as you can provide to help us better understand the nature and scope of the issue. diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 0000000..847cb61 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,3 @@ +# Support + +We use [GitHub discussions](https://github.com/SaladTechnologies/salad-cloud-imds-sdk-dotnet/discussions) for general chat and community-provided support. The community often chimes in with helpful advice when you have a question, and you may also find yourself providing answers and helping others. Be sure to review the [code of conduct](./CODE_OF_CONDUCT.md) before participating. diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..d43d809 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,32 @@ +name: Publish Package + +on: + release: + types: + - published + +env: + MANIFEST_PATH: .manifest.json + NUGET_API_SOURCE: https://api.nuget.org/v3/index.json + +jobs: + publish: + name: Publish Package + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Read .csproj file path + id: read_csproj_path + run: echo "csproj_path=$(jq -r '.files[] | select(endswith(".csproj") and (. | endswith("Example.csproj") | not))' $MANIFEST_PATH)" >> "$GITHUB_OUTPUT" + - name: Read SDK version + id: read_sdk_version + run: echo "sdk_version=$(jq -r '.config.sdkVersion' $MANIFEST_PATH)" >> "$GITHUB_OUTPUT" + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + - name: Pack NuGet package + run: dotnet pack ${{ steps.read_csproj_path.outputs.csproj_path }} -o ./dist --configuration Release /p:ContinuousIntegrationBuild=true /p:PackageVersion=${{ steps.read_sdk_version.outputs.sdk_version }} + - name: Publish NuGet package + run: dotnet nuget push ./dist/*.nupkg --api-key ${{ secrets.NUGET_TOKEN }} --source $NUGET_API_SOURCE diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..d782c81 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,41 @@ +name: Handle Stale Items + +on: + schedule: + - cron: "0 10 * * *" + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + stale: + name: Handle Stale Items + runs-on: ubuntu-latest + steps: + - name: Apply stale policy + uses: actions/stale@v8 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 30 + days-before-close: 7 + operations-per-run: 25 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "no-stale,help%20wanted,good%20first%20issue" + stale-issue-message: > + There hasn't been activity on this issue in 30 days. + + This issue has been marked stale and will be closed in 7 days if no further activity occurs. + + Issues with the labels `no-stale`, `help wanted`, and `good first issue` are exempt from this policy. + + stale-pr-label: "stale" + exempt-pr-labels: "no-stale" + stale-pr-message: > + There hasn't been any activity on this pull request in 30 days. + + This pull request has been marked stale and will be closed in 7 days if no further activity occurs. + + Pull requests with the label `no-stale` are exempt from this policy. diff --git a/.manifest.json b/.manifest.json new file mode 100644 index 0000000..17ebe9c --- /dev/null +++ b/.manifest.json @@ -0,0 +1,234 @@ +{ + "liblabVersion": "2.1.31", + "date": "2024-09-05T21:55:02.393Z", + "config": { + "apiId": 1126, + "sdkName": "salad-cloud-imds-sdk", + "sdkVersion": "0.9.0-alpha.1", + "liblabVersion": "2", + "deliveryMethods": ["zip"], + "languages": ["csharp"], + "specFilePath": "spec.yaml", + "docs": ["snippets"], + "languageOptions": { + "csharp": { + "packageId": "Salad.Cloud.IMDS.SDK", + "authors": [ + { + "name": "salad" + }, + { + "name": "seniorquico" + } + ], + "githubRepoName": "salad-cloud-imds-sdk-dotnet", + "homepage": "https://github.com/saladtechnologies/salad-cloud-imds-sdk-dotnet", + "ignoreFiles": [".gitignore", "LICENSE"], + "liblabVersion": "2", + "sdkVersion": "0.9.0-alpha.1", + "targetBranch": "main" + }, + "go": { + "goModuleName": "github.com/saladtechnologies/salad-cloud-imds-sdk-go", + "githubRepoName": "salad-cloud-imds-sdk-go", + "ignoreFiles": [".gitignore", "LICENSE"], + "liblabVersion": "2", + "sdkVersion": "0.9.0-alpha.1", + "targetBranch": "main" + }, + "java": { + "groupId": "com.salad.cloud", + "artifactId": "imds-sdk", + "developers": [ + { + "name": "SaladCloud Developers", + "email": "dev@salad.com", + "organization": "Salad Technologies", + "organizationUrl": "https://salad.com" + }, + { + "name": "Kyle Dodson", + "email": "kyle@salad.com", + "organization": "Salad Technologies", + "organizationUrl": "https://salad.com" + } + ], + "githubRepoName": "salad-cloud-imds-sdk-java", + "homepage": "https://github.com/saladtechnologies/salad-cloud-imds-sdk-java", + "ignoreFiles": [".gitignore", "LICENSE"], + "liblabVersion": "2", + "sdkVersion": "0.9.0-alpha.1", + "targetBranch": "main" + }, + "python": { + "alwaysInitializeOptionals": true, + "classifiers": [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Internet", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries" + ], + "projectUrls": { + "Homepage": "https://github.com/saladtechnologies/salad-cloud-imds-sdk-python", + "Documentation": "https://docs.salad.com", + "Repository": "https://github.com/SaladTechnologies/salad-cloud-imds-sdk-python.git", + "Issues": "https://github.com/SaladTechnologies/salad-cloud-imds-sdk-python/issues" + }, + "pypiPackageName": "salad-cloud-imds-sdk", + "authors": [ + { + "email": "dev@salad.com", + "name": "SaladCloud Developers" + }, + { + "email": "kyle@salad.com", + "name": "Kyle Dodson" + } + ], + "githubRepoName": "salad-cloud-imds-sdk-python", + "ignoreFiles": [".gitignore", "LICENSE"], + "liblabVersion": "2", + "sdkVersion": "0.9.0-alpha.1", + "targetBranch": "main" + }, + "typescript": { + "bundle": true, + "exportClassDefault": false, + "httpClient": "fetch", + "npmName": "salad-cloud-imds-sdk", + "npmOrg": "saladtechnologies-oss", + "authors": [ + { + "email": "dev@salad.com", + "name": "SaladCloud Developers" + }, + { + "email": "kyle@salad.com", + "name": "Kyle Dodson" + } + ], + "githubRepoName": "salad-cloud-imds-sdk-javascript", + "homepage": "https://github.com/saladtechnologies/salad-cloud-imds-sdk-javascript", + "ignoreFiles": [".gitignore", "LICENSE"], + "liblabVersion": "2", + "sdkVersion": "0.9.0-alpha.1", + "targetBranch": "main" + } + }, + "publishing": { + "githubOrg": "SaladTechnologies" + }, + "apiName": "SaladCloud IMDS", + "apiVersion": "0.9.0-alpha.1", + "devContainer": true, + "generateEnv": true, + "includeOptionalSnippetParameters": true, + "inferServiceNames": false, + "license": { + "type": "MIT", + "name": "MIT", + "url": "https://opensource.org/licenses/MIT", + "path": "MIT.ejs" + }, + "responseHeaders": false, + "retry": { + "enabled": true, + "maxAttempts": 3, + "retryDelay": 150, + "maxDelay": 5000, + "retryDelayJitter": 50, + "backOffFactor": 2, + "httpCodesToRetry": [408, 429, 500, 502, 503, 504], + "httpMethodsToRetry": ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"] + }, + "multiTenant": true, + "hooksLocation": { + "bucketKey": "7017/hooks.zip", + "bucketName": "prod-liblab-api-stack-hooks" + }, + "includeWatermark": false, + "packageId": "Salad.Cloud.IMDS.SDK", + "authors": [ + { + "name": "salad" + }, + { + "name": "seniorquico" + } + ], + "githubRepoName": "salad-cloud-imds-sdk-dotnet", + "homepage": "https://github.com/saladtechnologies/salad-cloud-imds-sdk-dotnet", + "ignoreFiles": [".gitignore", "LICENSE"], + "targetBranch": "main", + "language": "csharp", + "deliveryMethod": "zip", + "hooks": { + "enabled": true, + "sourceDir": "/tmp/resources/hooks" + }, + "usesFormData": false, + "authentication": {}, + "environmentVariables": [], + "fileOutput": "/tmp", + "httpLibrary": { + "name": "axios", + "packages": { + "axios": "^1.7.4" + }, + "languages": ["typescript"] + }, + "auth": [], + "customQueries": { + "paths": [], + "rawQueries": [], + "queriesData": [] + } + }, + "files": [ + "Salad.Cloud.IMDS.SDK/Salad.Cloud.IMDS.SDK.csproj", + "install.sh", + "Salad.Cloud.IMDS.SDK/Services/BaseService.cs", + "Salad.Cloud.IMDS.SDK/Hooks/IHook.cs", + "Salad.Cloud.IMDS.SDK/Hooks/CustomHook.cs", + "Salad.Cloud.IMDS.SDK/Http/Handlers/HookHandler.cs", + "Example/Example.csproj", + "Example/Program.cs", + "Example/.gitignore", + "Salad.Cloud.IMDS.SDK/Http/Serialization/SerializationStyle.cs", + "Salad.Cloud.IMDS.SDK/Http/Serialization/QuerySerializationStyle.cs", + "Salad.Cloud.IMDS.SDK/Http/Serialization/PathSerializationStyle.cs", + "Salad.Cloud.IMDS.SDK/Http/Serialization/Serializer.cs", + "Salad.Cloud.IMDS.SDK/Http/RequestBuilder.cs", + "Salad.Cloud.IMDS.SDK/Config/SaladCloudImdsSdkConfig.cs", + "Salad.Cloud.IMDS.SDK/Http/Environment.cs", + "Salad.Cloud.IMDS.SDK/SaladCloudImdsSdkClient.cs", + "Salad.Cloud.IMDS.SDK/Http/Handlers/RetryHandler.cs", + "Salad.Cloud.IMDS.SDK/.gitignore", + "./LICENSE", + "documentation/snippets/v1-reallocate-post.md", + "documentation/snippets/v1-status-get.md", + "documentation/snippets/v1-token-get.md", + "documentation/models/ReallocateContainer.md", + "documentation/models/ContainerStatus.md", + "documentation/models/ContainerToken.md", + ".devcontainer/devcontainer.json", + "Salad.Cloud.IMDS.SDK/Models/ReallocateContainer.cs", + "Salad.Cloud.IMDS.SDK/Models/ContainerStatus.cs", + "Salad.Cloud.IMDS.SDK/Models/ContainerToken.cs", + "Salad.Cloud.IMDS.SDK/Services/MetadataService.cs", + "documentation/services/MetadataService.md", + "README.md" + ] +} diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..1bb1ac7 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,11 @@ +endOfLine: "auto" +printWidth: 120 +semi: false +singleQuote: true +trailingComma: "all" +overrides: + - files: + - "*.yaml" + - "*.yml" + options: + singleQuote: false diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4df3368 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,12 @@ +{ + "recommendations": [ + "editorconfig.editorconfig", + "esbenp.prettier-vscode", + "github.vscode-github-actions", + "ms-dotnettools.csdevkit", + "ms-dotnettools.csharp", + "ms-vscode-remote.remote-containers", + "streetsidesoftware.code-spell-checker" + ], + "unwantedRecommendations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7805e34 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,26 @@ +{ + "dotnet.defaultSolution": "Salad.Cloud.IMDS.SDK.sln", + "editor.formatOnSave": true, + "editor.minimap.maxColumn": 120, + "editor.renderWhitespace": "all", + "editor.rulers": [120], + "files.associations": { + "CODEOWNERS": "ignore" + }, + "files.exclude": { + "**/bin": true, + "**/obj": true + }, + "git.branchProtection": ["main"], + "omnisharp.enableEditorConfigSupport": true, + "omnisharp.organizeImportsOnFormat": true, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + } +} diff --git a/Example/.gitignore b/Example/.gitignore new file mode 100644 index 0000000..8a30d25 --- /dev/null +++ b/Example/.gitignore @@ -0,0 +1,398 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/Example/Example.csproj b/Example/Example.csproj new file mode 100644 index 0000000..fbe8010 --- /dev/null +++ b/Example/Example.csproj @@ -0,0 +1,15 @@ + + + + + + + + Exe + net6.0 + enable + enable + + + + \ No newline at end of file diff --git a/Example/Program.cs b/Example/Program.cs new file mode 100644 index 0000000..2e15e0e --- /dev/null +++ b/Example/Program.cs @@ -0,0 +1,11 @@ +using Salad.Cloud.IMDS.SDK; +using Salad.Cloud.IMDS.SDK.Config; +using Environment = Salad.Cloud.IMDS.SDK.Http.Environment; + +var config = new SaladCloudImdsSdkConfig { Environment = Environment.Default }; + +var client = new SaladCloudImdsSdkClient(config); + +var response = await client.Metadata.GetContainerStatusAsync(); + +Console.WriteLine(response); diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a1611d5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Salad Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 5e9d307..b93b7c9 100644 --- a/README.md +++ b/README.md @@ -1 +1,92 @@ -# SaladCloud IMDS SDK for .NET +# SaladCloudImdsSdk C# SDK 0.9.0-alpha.1 + +Welcome to the SaladCloudImdsSdk SDK documentation. This guide will help you get started with integrating and using the SaladCloudImdsSdk SDK in your project. + +## Versions + +- API version: `0.9.0-alpha.1` +- SDK version: `0.9.0-alpha.1` + +## About the API + +The SaladCloud Instance Metadata Service (IMDS). Please refer to the [SaladCloud API Documentation](https://docs.salad.com/api-reference) for more details. + +## Table of Contents + +- [Setup & Configuration](#setup--configuration) + - [Supported Language Versions](#supported-language-versions) + - [Installation](#installation) +- [Sample Usage](#sample-usage) +- [Services](#services) +- [Models](#models) +- [License](#license) + +# Setup & Configuration + +## Supported Language Versions + +This SDK is compatible with the following versions: `C# >= .NET 6` + +## Installation + +To get started with the SDK, we recommend installing using `nuget`: + +```bash +dotnet add package Salad.Cloud.IMDS.SDK +``` + +# Sample Usage + +Below is a comprehensive example demonstrating how to authenticate and call a simple endpoint: + +```cs +using Salad.Cloud.IMDS.SDK; +using Salad.Cloud.IMDS.SDK.Config; +using Environment = Salad.Cloud.IMDS.SDK.Http.Environment; + +var config = new SaladCloudImdsSdkConfig +{ + Environment = Environment.Default +}; + +var client = new SaladCloudImdsSdkClient(config); + +var response = await client.Metadata.GetContainerStatusAsync(); + +Console.WriteLine(response); + +``` + +## Services + +The SDK provides various services to interact with the API. + +
+Below is a list of all available services with links to their detailed documentation: + +| Name | +| :----------------------------------------------------------- | +| [MetadataService](documentation/services/MetadataService.md) | + +
+ +## Models + +The SDK includes several models that represent the data structures used in API requests and responses. These models help in organizing and managing the data efficiently. + +
+Below is a list of all available models with links to their detailed documentation: + +| Name | Description | +| :----------------------------------------------------------------- | :------------------------------------------------------- | +| [ReallocateContainer](documentation/models/ReallocateContainer.md) | Represents a request to reallocate a container. | +| [ContainerStatus](documentation/models/ContainerStatus.md) | Represents the health statuses of the running container. | +| [ContainerToken](documentation/models/ContainerToken.md) | Represents the identity token of the running container. | + +
+ +## License + +This SDK is licensed under the MIT License. + +See the [LICENSE](LICENSE) file for more details. diff --git a/Salad.Cloud.IMDS.SDK.sln b/Salad.Cloud.IMDS.SDK.sln new file mode 100644 index 0000000..caf41cc --- /dev/null +++ b/Salad.Cloud.IMDS.SDK.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35222.181 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A50ACB9-3A05-4357-B191-B691065A800F}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example.csproj", "{5B5F1FBB-A574-49B5-8CDF-5A974B7DBD26}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Salad.Cloud.IMDS.SDK", "Salad.Cloud.IMDS.SDK\Salad.Cloud.IMDS.SDK.csproj", "{726F609B-4A21-4EE6-BC3F-CC58920C9293}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5B5F1FBB-A574-49B5-8CDF-5A974B7DBD26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B5F1FBB-A574-49B5-8CDF-5A974B7DBD26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B5F1FBB-A574-49B5-8CDF-5A974B7DBD26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B5F1FBB-A574-49B5-8CDF-5A974B7DBD26}.Release|Any CPU.Build.0 = Release|Any CPU + {726F609B-4A21-4EE6-BC3F-CC58920C9293}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {726F609B-4A21-4EE6-BC3F-CC58920C9293}.Debug|Any CPU.Build.0 = Debug|Any CPU + {726F609B-4A21-4EE6-BC3F-CC58920C9293}.Release|Any CPU.ActiveCfg = Release|Any CPU + {726F609B-4A21-4EE6-BC3F-CC58920C9293}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {46A4A6C6-EF0B-4C0D-96C7-1278EA5191B0} + EndGlobalSection +EndGlobal diff --git a/Salad.Cloud.IMDS.SDK/.gitignore b/Salad.Cloud.IMDS.SDK/.gitignore new file mode 100644 index 0000000..8a30d25 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/.gitignore @@ -0,0 +1,398 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/Salad.Cloud.IMDS.SDK/Config/SaladCloudImdsSdkConfig.cs b/Salad.Cloud.IMDS.SDK/Config/SaladCloudImdsSdkConfig.cs new file mode 100644 index 0000000..f5725e7 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Config/SaladCloudImdsSdkConfig.cs @@ -0,0 +1,11 @@ +using Environment = Salad.Cloud.IMDS.SDK.Http.Environment; + +namespace Salad.Cloud.IMDS.SDK.Config; + +/// +/// Configuration options for the SaladCloudImdsSdkClient. +/// +public record SaladCloudImdsSdkConfig( + /// The environment to use for the SDK. + Environment? Environment = null +); diff --git a/Salad.Cloud.IMDS.SDK/Hooks/CustomHook.cs b/Salad.Cloud.IMDS.SDK/Hooks/CustomHook.cs new file mode 100644 index 0000000..9a349ff --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Hooks/CustomHook.cs @@ -0,0 +1,20 @@ +namespace Salad.Cloud.IMDS.SDK.Hooks; + +public class CustomHook : IHook +{ + public Task BeforeRequestAsync(HttpRequestMessage request) + { + request.Headers.Add("Metadata", "true"); + return Task.FromResult(request); + } + + public Task AfterResponseAsync(HttpResponseMessage response) + { + return Task.FromResult(response); + } + + public Task OnErrorAsync(HttpResponseMessage response) + { + return Task.CompletedTask; + } +} diff --git a/Salad.Cloud.IMDS.SDK/Hooks/IHook.cs b/Salad.Cloud.IMDS.SDK/Hooks/IHook.cs new file mode 100644 index 0000000..9a4cf08 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Hooks/IHook.cs @@ -0,0 +1,8 @@ +namespace Salad.Cloud.IMDS.SDK.Hooks; + +public interface IHook +{ + public Task BeforeRequestAsync(HttpRequestMessage request); + public Task AfterResponseAsync(HttpResponseMessage response); + public Task OnErrorAsync(HttpResponseMessage response); +} diff --git a/Salad.Cloud.IMDS.SDK/Http/Environment.cs b/Salad.Cloud.IMDS.SDK/Http/Environment.cs new file mode 100644 index 0000000..9097a93 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Http/Environment.cs @@ -0,0 +1,16 @@ +namespace Salad.Cloud.IMDS.SDK.Http; + +/// +/// The environments available for the SDK. +/// +public class Environment +{ + internal Uri Uri { get; private set; } + + private Environment(string uri) + { + Uri = new Uri(uri); + } + + public static Environment Default { get; } = new("http://169.254.169.254/"); +} diff --git a/Salad.Cloud.IMDS.SDK/Http/Handlers/HookHandler.cs b/Salad.Cloud.IMDS.SDK/Http/Handlers/HookHandler.cs new file mode 100644 index 0000000..7b0f1ef --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Http/Handlers/HookHandler.cs @@ -0,0 +1,33 @@ +using Salad.Cloud.IMDS.SDK.Hooks; + +namespace Salad.Cloud.IMDS.SDK.Http.Handlers; + +/// +/// A handler for executing lifecycle hooks for the request. +/// +public class HookHandler : DelegatingHandler +{ + private readonly IHook hook; + + internal HookHandler(IHook hook, HttpMessageHandler? innerHandler = null) + : base(innerHandler ?? new HttpClientHandler()) + { + this.hook = hook; + } + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken + ) + { + request = await this.hook.BeforeRequestAsync(request); + + var response = await base.SendAsync(request, cancellationToken); + + if (response.IsSuccessStatusCode) + return await this.hook.AfterResponseAsync(response); + + await this.hook.OnErrorAsync(response); + return response; + } +} diff --git a/Salad.Cloud.IMDS.SDK/Http/Handlers/RetryHandler.cs b/Salad.Cloud.IMDS.SDK/Http/Handlers/RetryHandler.cs new file mode 100644 index 0000000..4bdedb8 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Http/Handlers/RetryHandler.cs @@ -0,0 +1,55 @@ +using System.Net; +using Polly; +using Polly.Retry; + +namespace Salad.Cloud.IMDS.SDK.Http.Handlers; + +/// +/// A handler for retrying requests when they fail. +/// +public class RetryHandler : DelegatingHandler +{ + public RetryHandler(HttpMessageHandler? innerHandler = null) + : base(innerHandler ?? new HttpClientHandler()) { } + + private static readonly Func< + RetryPredicateArguments, + ValueTask + > TransientHttpStatusCodePredicate = (args) => + { + var response = args.Outcome.Result; + + if (response is null) + { + return ValueTask.FromResult(false); + } + + var isTransientStatusCode = + (int)response.StatusCode >= 500 || response.StatusCode == HttpStatusCode.RequestTimeout; + return ValueTask.FromResult(isTransientStatusCode); + }; + + private readonly ResiliencePipeline _pipeline = + new ResiliencePipelineBuilder() + .AddRetry( + new RetryStrategyOptions() + { + ShouldHandle = TransientHttpStatusCodePredicate, + Delay = TimeSpan.FromMilliseconds(150), + BackoffType = DelayBackoffType.Exponential, + MaxRetryAttempts = 3, + } + ) + .Build(); + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken + ) + { + return await _pipeline.ExecuteAsync( + async (token) => await base.SendAsync(request, token), + cancellationToken + ); + } +} diff --git a/Salad.Cloud.IMDS.SDK/Http/RequestBuilder.cs b/Salad.Cloud.IMDS.SDK/Http/RequestBuilder.cs new file mode 100644 index 0000000..af77291 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Http/RequestBuilder.cs @@ -0,0 +1,152 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using Salad.Cloud.IMDS.SDK.Http.Serialization; + +namespace Salad.Cloud.IMDS.SDK.Http; + +/// +/// A builder for creating instances with full support for serialization. +/// +public class RequestBuilder +{ + private readonly string _urlTemplate; + + private readonly HttpMethod _httpMethod; + + private readonly Dictionary _pathParameters = new(); + private readonly List _queryParameters = new(); + private readonly Dictionary _headers = new(); + + private HttpContent? _content; + + /// + /// Initializes a new instance of the class. + /// + /// The HTTP method to use for the request. + /// The URL template to use for the request. Should have path parameters as placeholders surrounded by brackets (Eg. "/users/{id}", where "id" is a path parameter). + public RequestBuilder(HttpMethod httpMethod, string urlTemplate) + { + _httpMethod = httpMethod; + _urlTemplate = urlTemplate; + } + + /// + /// Sets a path parameter. If the parameter is not present in the URL template, it will be ignored. + /// + public RequestBuilder SetPathParameter( + string key, + object value, + PathSerializationStyle style = PathSerializationStyle.Simple, + bool explode = false + ) + { + var serializedValue = Serializer.Serialize(key, value, (SerializationStyle)style, explode); + _pathParameters.Add(key, serializedValue); + return this; + } + + /// + /// Sets a query parameter. + /// + public RequestBuilder SetQueryParameter( + string key, + object? value, + QuerySerializationStyle style = QuerySerializationStyle.Form, + bool explode = true + ) + { + var serializedValue = Serializer.Serialize(key, value, (SerializationStyle)style, explode); + if (!string.IsNullOrEmpty(serializedValue)) + { + _queryParameters.Add(serializedValue); + } + return this; + } + + /// + /// Sets a query parameter if the value is not null. + /// + public RequestBuilder SetOptionalQueryParameter( + string key, + object? value, + QuerySerializationStyle style = QuerySerializationStyle.Form, + bool explode = true + ) + { + if (value is not null) + { + SetQueryParameter(key, value, style, explode); + } + return this; + } + + /// + /// Sets a header. + /// + public RequestBuilder SetHeader(string key, object? value, bool explode = false) + { + var serializedValue = Serializer.Serialize(key, value, SerializationStyle.Simple, explode); + if (!string.IsNullOrEmpty(serializedValue)) + { + _headers.Add(key, serializedValue); + } + return this; + } + + /// + /// Sets a header if the value is not null. + /// + public RequestBuilder SetOptionalHeader(string key, object? value, bool explode = false) + { + if (value is not null) + { + SetHeader(key, value, explode); + } + return this; + } + + /// + /// Sets the content of the request as JSON. + /// + public RequestBuilder SetContentAsJson( + object content, + JsonSerializerOptions? options = null, + MediaTypeHeaderValue? mediaType = null + ) + { + _content = JsonContent.Create(content, mediaType, options); + return this; + } + + private string BuildUrl() + { + var url = _urlTemplate; + foreach (var (key, value) in _pathParameters) + { + url = url.Replace($"{{{key}}}", value); + } + + if (_queryParameters.Any()) + { + url += "?" + string.Join("&", _queryParameters); + } + + return url; + } + + /// + /// Builds the instance. + /// + public HttpRequestMessage Build() + { + var requestMessage = new HttpRequestMessage(_httpMethod, BuildUrl()) { Content = _content }; + + foreach (var (key, value) in _headers) + { + requestMessage.Headers.Add(key, value); + } + + return requestMessage; + } +} diff --git a/Salad.Cloud.IMDS.SDK/Http/Serialization/PathSerializationStyle.cs b/Salad.Cloud.IMDS.SDK/Http/Serialization/PathSerializationStyle.cs new file mode 100644 index 0000000..aab2c1e --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Http/Serialization/PathSerializationStyle.cs @@ -0,0 +1,8 @@ +namespace Salad.Cloud.IMDS.SDK.Http.Serialization; + +public enum PathSerializationStyle +{ + Simple = SerializationStyle.Simple, + Label = SerializationStyle.Label, + Matrix = SerializationStyle.Matrix +} diff --git a/Salad.Cloud.IMDS.SDK/Http/Serialization/QuerySerializationStyle.cs b/Salad.Cloud.IMDS.SDK/Http/Serialization/QuerySerializationStyle.cs new file mode 100644 index 0000000..1ed6a02 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Http/Serialization/QuerySerializationStyle.cs @@ -0,0 +1,9 @@ +namespace Salad.Cloud.IMDS.SDK.Http.Serialization; + +public enum QuerySerializationStyle +{ + Form = SerializationStyle.Form, + SpaceDelimited = SerializationStyle.SpaceDelimited, + PipeDelimited = SerializationStyle.PipeDelimited, + DeepObject = SerializationStyle.DeepObject +} diff --git a/Salad.Cloud.IMDS.SDK/Http/Serialization/SerializationStyle.cs b/Salad.Cloud.IMDS.SDK/Http/Serialization/SerializationStyle.cs new file mode 100644 index 0000000..0ea8008 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Http/Serialization/SerializationStyle.cs @@ -0,0 +1,12 @@ +namespace Salad.Cloud.IMDS.SDK.Http.Serialization; + +public enum SerializationStyle +{ + Simple = 0, + Label = 1, + Matrix = 2, + Form = 3, + SpaceDelimited = 4, + PipeDelimited = 5, + DeepObject = 6 +} diff --git a/Salad.Cloud.IMDS.SDK/Http/Serialization/Serializer.cs b/Salad.Cloud.IMDS.SDK/Http/Serialization/Serializer.cs new file mode 100644 index 0000000..f94c71b --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Http/Serialization/Serializer.cs @@ -0,0 +1,164 @@ +using System.Collections; +using System.Net; +using System.Text.Json.Serialization; + +namespace Salad.Cloud.IMDS.SDK.Http.Serialization; + +public static class Serializer +{ + /// + /// Serializes types into strings based on the RFC-6570 URI Template and OpenAPI specifications. + /// + /// + /// + public static string Serialize( + string key, + T value, + SerializationStyle style, + bool explode = true + ) + { + return value switch + { + null + or string + or bool + or int + or long + or double + => SerializePrimitive(key, value, style), + IEnumerable e => SerializeEnumerable(key, e, style, explode), + object o => SerializeObject(key, o, style, explode), + }; + } + + private static string SerializeValue(object? value) + { + return value switch + { + null => "null", + string s => WebUtility.UrlEncode(s), + bool b => b.ToString().ToLowerInvariant(), + int or long or double => value.ToString(), + IEnumerable e + => SerializeEnumerable(string.Empty, e, SerializationStyle.Simple, false), + not null => SerializeObject(string.Empty, value, SerializationStyle.Simple, false) + } ?? string.Empty; + } + + private static string SerializePrimitive(string key, object? value, SerializationStyle style) + { + return style switch + { + SerializationStyle.Label => $".{SerializeValue(value)}", + SerializationStyle.Matrix => $";{SerializeValue(value)}", + SerializationStyle.Form => $"{key}={SerializeValue(value)}", + _ => SerializeValue(value) + }; + } + + private static string SerializeEnumerable( + string key, + IEnumerable enumerable, + SerializationStyle style, + bool explode + ) + { + var array = enumerable as object[] ?? enumerable.Cast().ToArray(); + var serializedValues = array.Select(SerializeValue); + + switch (style) + { + case SerializationStyle.Simple: + return string.Join(",", serializedValues); + case SerializationStyle.Label: + return explode + ? string.Join(".", serializedValues) + : $".{string.Join(",", serializedValues)}"; + case SerializationStyle.Matrix: + return explode + ? string.Join($";{key}=", serializedValues) + : $";{key}={string.Join(",", serializedValues)}"; + } + + if (explode) + return string.Join( + $"&", + array.Select(e => Serialize(key, e, SerializationStyle.Form, explode)) + ); + + var separator = style switch + { + SerializationStyle.SpaceDelimited => " ", + SerializationStyle.PipeDelimited => "|", + _ => "," + }; + + return $"{key}={string.Join(separator, serializedValues)}"; + } + + private static string SerializeObject( + string key, + T o, + SerializationStyle style, + bool explode + ) + where T : class + { + var properties = o.GetType() + .GetProperties() + .Select(p => + { + // Use JsonPropertyNameAttribute for property name if available + var name = p.GetCustomAttributes(typeof(JsonPropertyNameAttribute), false) + .FirstOrDefault() + is JsonPropertyNameAttribute jsonProperty + ? jsonProperty.Name + : p.Name; + return (name, value: p.GetValue(o)); + }) + .Where(p => p.value != null); + + switch (style) + { + case SerializationStyle.Simple: + return string.Join( + ",", + explode + ? properties.Select(p => $"{p.name}={SerializeValue(p.value)}") + : properties.Select(p => $"{p.name},{SerializeValue(p.value)}") + ); + case SerializationStyle.Label: + return explode + ? string.Join( + ".", + properties.Select(p => $"{p.name}={SerializeValue(p.value)}") + ) + : $"." + + string.Join( + ",", + properties.Select(p => $"{p.name}={SerializeValue(p.value)}") + ); + case SerializationStyle.Matrix: + return explode + ? string.Join( + $";", + properties.Select(p => $"{p.name}={SerializeValue(p.value)}") + ) + : $";{string.Join(",", properties.Select(p => $"{p.name}={SerializeValue(p.value)}"))}"; + case SerializationStyle.DeepObject: + return string.Join( + "&", + properties.Select(p => $"{key}[{p.name}]={SerializeValue(p.value)}") + ); + default: + // Form style + return explode + ? string.Join( + "&", + properties.Select(p => $"{p.name}={SerializeValue(p.value)}") + ) + : $"{key}={string.Join(",", properties.Select((p) => $"{p.name},{SerializeValue(p.value)}"))}"; + } + } +} diff --git a/Salad.Cloud.IMDS.SDK/Models/ContainerStatus.cs b/Salad.Cloud.IMDS.SDK/Models/ContainerStatus.cs new file mode 100644 index 0000000..43a98b6 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Models/ContainerStatus.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Salad.Cloud.IMDS.SDK.Models; + +/// Represents the health statuses of the running container. +public record ContainerStatus( + /// `true` if the running container is ready. If a readiness probe is defined, this returns the latest result of the probe. If a readiness probe is not defined but a startup probe is defined, this returns the same value as the `started` property. If neither a readiness probe nor a startup probe are defined, returns `true`. + [property: JsonPropertyName("ready")] + bool Ready, + /// `true` if the running container is started. If a startup probe is defined, this returns the latest result of the probe. If a startup probe is not defined, returns `true`. + [property: JsonPropertyName("started")] + bool Started +); diff --git a/Salad.Cloud.IMDS.SDK/Models/ContainerToken.cs b/Salad.Cloud.IMDS.SDK/Models/ContainerToken.cs new file mode 100644 index 0000000..70f8977 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Models/ContainerToken.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Salad.Cloud.IMDS.SDK.Models; + +/// Represents the identity token of the running container. +public record ContainerToken( + /// The JSON Web Token (JWT) that may be used to identify the running container. The JWT may be verified using the JSON Web Key Set (JWKS) available at https://matrix-rest-api.salad.com/.well-known/stash-jwks.json. + [property: JsonPropertyName("jwt")] + string Jwt +); diff --git a/Salad.Cloud.IMDS.SDK/Models/ReallocateContainer.cs b/Salad.Cloud.IMDS.SDK/Models/ReallocateContainer.cs new file mode 100644 index 0000000..c38f43c --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Models/ReallocateContainer.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Salad.Cloud.IMDS.SDK.Models; + +/// Represents a request to reallocate a container. +public record ReallocateContainer( + /// The reason for reallocating the container. This value is reported to SaladCloud support for quality assurance of Salad Nodes. + [property: JsonPropertyName("reason")] + string Reason +); diff --git a/Salad.Cloud.IMDS.SDK/Salad.Cloud.IMDS.SDK.csproj b/Salad.Cloud.IMDS.SDK/Salad.Cloud.IMDS.SDK.csproj new file mode 100644 index 0000000..b1b180b --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Salad.Cloud.IMDS.SDK.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + enable + Salad.Cloud.IMDS.SDK + 0.9.0-alpha.1 + Salad.Cloud.IMDS.SDK + salad, seniorquico + https://github.com/saladtechnologies/salad-cloud-imds-sdk-dotnet + README.md + true + true + true + true + snupkg + + + + + + + + + diff --git a/Salad.Cloud.IMDS.SDK/SaladCloudImdsSdkClient.cs b/Salad.Cloud.IMDS.SDK/SaladCloudImdsSdkClient.cs new file mode 100644 index 0000000..2c11ac6 --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/SaladCloudImdsSdkClient.cs @@ -0,0 +1,58 @@ +using Salad.Cloud.IMDS.SDK.Config; +using Salad.Cloud.IMDS.SDK.Hooks; +using Salad.Cloud.IMDS.SDK.Http.Handlers; +using Salad.Cloud.IMDS.SDK.Services; +using Environment = Salad.Cloud.IMDS.SDK.Http.Environment; + +namespace Salad.Cloud.IMDS.SDK; + +public class SaladCloudImdsSdkClient : IDisposable +{ + private readonly HttpClient _httpClient; + + public MetadataService Metadata { get; private set; } + + public SaladCloudImdsSdkClient(SaladCloudImdsSdkConfig? config = null) + { + var hookHandler = new HookHandler(new CustomHook()); + var retryHandler = new RetryHandler(hookHandler); + _httpClient = new HttpClient(retryHandler) + { + BaseAddress = config?.Environment?.Uri ?? Environment.Default.Uri, + DefaultRequestHeaders = { { "user-agent", "dotnet/7.0" } } + }; + + Metadata = new MetadataService(_httpClient); + } + + /// + /// Set the environment for the entire SDK. + /// + public void SetEnvironment(Environment environment) + { + SetBaseUrl(environment.Uri); + } + + /// + /// Sets the base URL for entire SDK. + /// + public void SetBaseUrl(string baseUrl) + { + SetBaseUrl(new Uri(baseUrl)); + } + + /// + /// Sets the base URL for the entire SDK. + /// + public void SetBaseUrl(Uri uri) + { + _httpClient.BaseAddress = uri; + } + + public void Dispose() + { + _httpClient.Dispose(); + } +} + +// c029837e0e474b76bc487506e8799df5e3335891efe4fb02bda7a1441840310c diff --git a/Salad.Cloud.IMDS.SDK/Services/BaseService.cs b/Salad.Cloud.IMDS.SDK/Services/BaseService.cs new file mode 100644 index 0000000..db51bce --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Services/BaseService.cs @@ -0,0 +1,15 @@ +using System.Text.Json; + +namespace Salad.Cloud.IMDS.SDK.Services; + +public class BaseService +{ + protected readonly HttpClient _httpClient; + protected readonly JsonSerializerOptions _jsonSerializerOptions; + + public BaseService(HttpClient httpClient) + { + _httpClient = httpClient; + _jsonSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); + } +} diff --git a/Salad.Cloud.IMDS.SDK/Services/MetadataService.cs b/Salad.Cloud.IMDS.SDK/Services/MetadataService.cs new file mode 100644 index 0000000..2074b6f --- /dev/null +++ b/Salad.Cloud.IMDS.SDK/Services/MetadataService.cs @@ -0,0 +1,70 @@ +using System.Net.Http.Json; +using Salad.Cloud.IMDS.SDK.Http; +using Salad.Cloud.IMDS.SDK.Http.Serialization; +using Salad.Cloud.IMDS.SDK.Models; + +namespace Salad.Cloud.IMDS.SDK.Services; + +public class MetadataService : BaseService +{ + internal MetadataService(HttpClient httpClient) + : base(httpClient) { } + + /// Reallocates the running container to another Salad Node + public async Task ReallocateContainerAsync( + ReallocateContainer input, + CancellationToken cancellationToken = default + ) + { + ArgumentNullException.ThrowIfNull(input, nameof(input)); + + var request = new RequestBuilder(HttpMethod.Post, "v1/reallocate") + .SetContentAsJson(input, _jsonSerializerOptions) + .Build(); + + var response = await _httpClient + .SendAsync(request, cancellationToken) + .ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + } + + /// Gets the health statuses of the running container + public async Task GetContainerStatusAsync( + CancellationToken cancellationToken = default + ) + { + var request = new RequestBuilder(HttpMethod.Get, "v1/status").Build(); + + var response = await _httpClient + .SendAsync(request, cancellationToken) + .ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + return await response + .Content.ReadFromJsonAsync( + _jsonSerializerOptions, + cancellationToken + ) + .ConfigureAwait(false) ?? throw new Exception("Failed to deserialize response."); + } + + /// Gets the identity token of the running container + public async Task GetContainerTokenAsync( + CancellationToken cancellationToken = default + ) + { + var request = new RequestBuilder(HttpMethod.Get, "v1/token").Build(); + + var response = await _httpClient + .SendAsync(request, cancellationToken) + .ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + return await response + .Content.ReadFromJsonAsync( + _jsonSerializerOptions, + cancellationToken + ) + .ConfigureAwait(false) ?? throw new Exception("Failed to deserialize response."); + } +} diff --git a/documentation/models/ContainerStatus.md b/documentation/models/ContainerStatus.md new file mode 100644 index 0000000..bf9ffa7 --- /dev/null +++ b/documentation/models/ContainerStatus.md @@ -0,0 +1,10 @@ +# ContainerStatus + +Represents the health statuses of the running container. + +**Properties** + +| Name | Type | Required | Description | +| :------ | :--- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Ready | bool | ✅ | `true` if the running container is ready. If a readiness probe is defined, this returns the latest result of the probe. If a readiness probe is not defined but a startup probe is defined, this returns the same value as the `started` property. If neither a readiness probe nor a startup probe are defined, returns `true`. | +| Started | bool | ✅ | `true` if the running container is started. If a startup probe is defined, this returns the latest result of the probe. If a startup probe is not defined, returns `true`. | diff --git a/documentation/models/ContainerToken.md b/documentation/models/ContainerToken.md new file mode 100644 index 0000000..7c78a3f --- /dev/null +++ b/documentation/models/ContainerToken.md @@ -0,0 +1,9 @@ +# ContainerToken + +Represents the identity token of the running container. + +**Properties** + +| Name | Type | Required | Description | +| :--- | :----- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Jwt | string | ✅ | The JSON Web Token (JWT) that may be used to identify the running container. The JWT may be verified using the JSON Web Key Set (JWKS) available at https://matrix-rest-api.salad.com/.well-known/stash-jwks.json. | diff --git a/documentation/models/ReallocateContainer.md b/documentation/models/ReallocateContainer.md new file mode 100644 index 0000000..b55427c --- /dev/null +++ b/documentation/models/ReallocateContainer.md @@ -0,0 +1,9 @@ +# ReallocateContainer + +Represents a request to reallocate a container. + +**Properties** + +| Name | Type | Required | Description | +| :----- | :----- | :------- | :---------------------------------------------------------------------------------------------------------------------------- | +| Reason | string | ✅ | The reason for reallocating the container. This value is reported to SaladCloud support for quality assurance of Salad Nodes. | diff --git a/documentation/services/MetadataService.md b/documentation/services/MetadataService.md new file mode 100644 index 0000000..8c0bee6 --- /dev/null +++ b/documentation/services/MetadataService.md @@ -0,0 +1,81 @@ +# MetadataService + +A list of all methods in the `MetadataService` service. Click on the method name to view detailed information about that method. + +| Methods | Description | +| :---------------------------------------------------- | :------------------------------------------------------ | +| [ReallocateContainerAsync](#reallocatecontainerasync) | Reallocates the running container to another Salad Node | +| [GetContainerStatusAsync](#getcontainerstatusasync) | Gets the health statuses of the running container | +| [GetContainerTokenAsync](#getcontainertokenasync) | Gets the identity token of the running container | + +## ReallocateContainerAsync + +Reallocates the running container to another Salad Node + +- HTTP Method: `POST` +- Endpoint: `/v1/reallocate` + +**Parameters** + +| Name | Type | Required | Description | +| :---- | :------------------ | :------- | :---------------- | +| input | ReallocateContainer | ✅ | The request body. | + +**Example Usage Code Snippet** + +```csharp +using Salad.Cloud.IMDS.SDK; +using Salad.Cloud.IMDS.SDK.Models; + +var client = new SaladCloudImdsSdkClient(); + +var input = new ReallocateContainer("commodo"); + +await client.Metadata.ReallocateContainerAsync(input); +``` + +## GetContainerStatusAsync + +Gets the health statuses of the running container + +- HTTP Method: `GET` +- Endpoint: `/v1/status` + +**Return Type** + +`ContainerStatus` + +**Example Usage Code Snippet** + +```csharp +using Salad.Cloud.IMDS.SDK; + +var client = new SaladCloudImdsSdkClient(); + +var response = await client.Metadata.GetContainerStatusAsync(); + +Console.WriteLine(response); +``` + +## GetContainerTokenAsync + +Gets the identity token of the running container + +- HTTP Method: `GET` +- Endpoint: `/v1/token` + +**Return Type** + +`ContainerToken` + +**Example Usage Code Snippet** + +```csharp +using Salad.Cloud.IMDS.SDK; + +var client = new SaladCloudImdsSdkClient(); + +var response = await client.Metadata.GetContainerTokenAsync(); + +Console.WriteLine(response); +``` diff --git a/documentation/snippets/v1-reallocate-post.md b/documentation/snippets/v1-reallocate-post.md new file mode 100644 index 0000000..f3c2d96 --- /dev/null +++ b/documentation/snippets/v1-reallocate-post.md @@ -0,0 +1,12 @@ +```csharp +using Salad.Cloud.IMDS.SDK; +using Salad.Cloud.IMDS.SDK.Models; + +var client = new SaladCloudImdsSdkClient(); + +var input = new ReallocateContainer("commodo"); + +await client.Metadata.ReallocateContainerAsync(input); + + +``` diff --git a/documentation/snippets/v1-status-get.md b/documentation/snippets/v1-status-get.md new file mode 100644 index 0000000..49d8612 --- /dev/null +++ b/documentation/snippets/v1-status-get.md @@ -0,0 +1,10 @@ +```csharp +using Salad.Cloud.IMDS.SDK; + +var client = new SaladCloudImdsSdkClient(); + +var response = await client.Metadata.GetContainerStatusAsync(); + +Console.WriteLine(response); + +``` diff --git a/documentation/snippets/v1-token-get.md b/documentation/snippets/v1-token-get.md new file mode 100644 index 0000000..af0a64d --- /dev/null +++ b/documentation/snippets/v1-token-get.md @@ -0,0 +1,10 @@ +```csharp +using Salad.Cloud.IMDS.SDK; + +var client = new SaladCloudImdsSdkClient(); + +var response = await client.Metadata.GetContainerTokenAsync(); + +Console.WriteLine(response); + +``` diff --git a/global.json b/global.json new file mode 100644 index 0000000..94aa859 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "allowPrerelease": true, + "rollForward": "latestFeature", + "version": "8.0.401" + } +} diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..93bea53 --- /dev/null +++ b/install.sh @@ -0,0 +1,3 @@ +cd ./Salad.Cloud.IMDS.SDK +dotnet restore +dotnet build