Skip to content

Commit

Permalink
Feat: Add both Azure and local-dev object-store recipes
Browse files Browse the repository at this point in the history
Signed-off-by: SoTrx <[email protected]>
  • Loading branch information
SoTrx committed Nov 21, 2024
1 parent 585fafd commit bc16b16
Show file tree
Hide file tree
Showing 2 changed files with 359 additions and 0 deletions.
114 changes: 114 additions & 0 deletions azure/daprbindingobjectstore.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
extension kubernetes with {
kubeConfig: ''
namespace: context.runtime.kubernetes.namespace
} as k8s
extension radius

@description('Information about what resource is calling this Recipe. Generated by Radius. For more information visit https://docs.radapp.dev/operations/custom-recipes/')
param context object

@description('Name of the storage account to use')
param accountName string

@description('Optional storage account key as a secret reference to a Dapr secret store. If this is not provided, either the worload identity will be used or the account key will be fetched from the storage account reference.')
param accountKeyRef SecretStoreReference = {
secretStoreName: ''
secretKeyRef: {
name: 'accountKey'
key: 'accountKey'
}
}

@description('Wether to use workload identity to access the storage account. Default is false')
param useWorkloadIdentity bool = false

@description('Name of the bucket to create in the S3-compatible object store. Default is mybucket')
param bucket string = 'mybucket'

@description('Encode binary files content in base64 before sending it to the application. Default is true')
param decodeBase64 bool = true



resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = {
name: accountName
}

var accountKeyMetadata = useWorkloadIdentity
// If workload identity is used, the account key is not needed.
? {}
// If workload identity is not used, the account key can be provided as a secret or a value.
// As a secret, the secret store name must be provided.
: !empty(accountKeyRef.secretStoreName)
? {
name: 'accountKey'
secretKeyRef: accountKeyRef.secretKeyRef
}
// As a value, the account key can be fetched from the storage account reference.
: {
name: 'accountKey'
value: storageAccount.listKeys().keys[0].value
}

// The secret store name is only needed if the account key is provided as a secret.
var daprAuthProperty = !empty(accountKeyRef.secretKeyRef)
? { secretStore: accountKeyRef.secretStoreName }
: { secretStore: '' }


var daprType = 'bindings.azure.blobstorage'
var daprVersion = 'v1'
resource daprComponent 'dapr.io/Component@v1alpha1' = {
auth: daprAuthProperty
metadata: {
name: context.resource.name
}
spec: {
type: daprType
version: daprVersion
metadata: concat(
[
{
name: 'accountName'
value: storageAccount.name
}
{
name: 'containerName'
value: bucket
}
{
name: 'decodeBase64'
value: decodeBase64
}
],
[accountKeyMetadata]
)
}
}

@description('Reference to a secret in a Dapr secret store')
type SecretStoreReference = {
@description('Name of the secret store')
secretStoreName: string
@description('Reference to a key in the secret store')
secretKeyRef: {
@description('Name of the secret')
name: string
@description('Key of the secret if it is a dictionary')
key: string
}
}

output result object = {
// This workaround is needed because the deployment engine omits Kubernetes resources from its output.
// This allows Kubernetes resources to be cleaned up when the resource is deleted.
// Once this gap is addressed, users won't need to do this.
resources: [
'/planes/kubernetes/local/namespaces/${daprComponent.metadata.namespace}/providers/dapr.io/Component/${daprComponent.metadata.name}'
]
values: {
type: daprType
version: daprVersion
metadata: daprComponent.spec.metadata
}
}
245 changes: 245 additions & 0 deletions local-dev/daprbindingobjectstore.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/*
Copyright 2023 The Radius Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
@description('Information about what resource is calling this Recipe. Generated by Radius. For more information visit https://docs.radapp.dev/operations/custom-recipes/')
param context object

@description('Name of the bucket to create in the S3-compatible object store')
param bucket string = 'mybucket'

@description('Tag to pull for the minio container image.')
param tag string = 'latest'

@description('Default admin username for the MinIO object store')
param minioUser string = 'minioadmin'

@secure()
@description('Default admin password for the MinIO object store')
#disable-next-line secure-parameter-default
param minioPassword string = 'minioadmin'

@description('Port for the MinIO object store S3-like API')
param minioApiPort int = 9000

@description('Port for the MinIO object store Web UI')
param minioWebPort int = 9001

@description('Binary files must be converted base64 before being sent to the object store')
param encodeBase64 bool = true

@description('Encode binary files content in base64 before sending it to the application')
param decodeBase64 bool = true


extension kubernetes with {
kubeConfig: ''
namespace: context.runtime.kubernetes.namespace
} as kubernetes

var uniqueName = 'daprbindingobj-${uniqueString(context.resource.id)}'

resource minio 'apps/Deployment@v1' = {
metadata: {
name: uniqueName
}
spec: {
replicas: 1
selector: {
matchLabels: {
app: 'minio-app'
resource: context.resource.name
}
}
template: {
metadata: {
labels: {
app: 'minio-app'
resource: context.resource.name
'radapp.io/application': context.application == null ? '' : context.application.name
}
}
spec: {
// This initContainer creates the bucket in the MinIO object store
// This works because the MinIO container is configured to use an emptyDir volume
// So in this case a bucket is a directory
initContainers: [
{
name: 'bucket-creation'
image: 'busybox:1.28'
command: [
'/bin/sh', '-c'
'echo "Creating bucket ${bucket}" && mkdir -p /data/${bucket}'
]
volumeMounts: [
{
name: 'data'
mountPath: '/data'
}
]
}
]
containers: [
{
name: 'minio'
image: 'minio/minio:${tag}'
args: [
'server'
'--address'
':${minioApiPort}'
// Console address refers to the Web UI, somehow
'--console-address'
':${minioWebPort}'
'/data'
]
ports: [
{ containerPort: minioApiPort }
{ containerPort: minioWebPort }
]
env: [
{
name: 'MINIO_ROOT_USER'
value: minioUser
}
{
name: 'MINIO_ROOT_PASSWORD'
value: minioPassword
}
// The browser animation will break port-forwarding
// https://github.com/minio/console/issues/2539
{
name: 'MINIO_BROWSER_LOGIN_ANIMATION'
value: 'off'
}
]
volumeMounts: [
{
name: 'data'
mountPath: '/data'
}
]
}
]
volumes: [
{
name: 'data'
emptyDir: {} // In-memory storage, not persistent (for dev/test)
}
]
}
}
}
}

resource svc 'core/Service@v1' = {
metadata: {
name: uniqueName
}
spec: {
type: 'ClusterIP'
selector: {
app: 'minio-app'
resource: context.resource.name
}
ports: [
{
name: 'api'
port: minioApiPort
targetPort: minioApiPort
}
{
name: 'ui'
port: minioWebPort
targetPort: minioWebPort
}
]
}
}



var daprType = 'bindings.aws.s3'
var daprVersion = 'v1'
resource daprComponent 'dapr.io/Component@v1alpha1' = {
metadata: {
name: context.resource.name
}
spec: {
type: daprType
version: daprVersion
metadata: [
{
name: 'endpoint'
value: '${svc.metadata.name}.${svc.metadata.namespace}.svc.cluster.local:${minioApiPort}'
}
{
name: 'bucket'
value: bucket
}
// The admin credentials are used by the Dapr sidecar to access the object store
// In a production scenario, another user with fewer permissions should be used
{
name: 'accessKey'
value: minioUser
}
{
name: 'secretKey'
value: minioPassword
}
// Binary files must be converted to base64 to be handled by Dapr
// This adds some overhead, so it can be disabled if not needed
{
name: 'encodeBase64'
value: encodeBase64 ? 'true' : 'false'
}
{
name: 'decodeBase64'
value: decodeBase64 ? 'true' : 'false'
}
// The next three values are recommended for MinIO by the documentation
// https://docs.dapr.io/reference/components-reference/supported-bindings/s3/#s3-bucket-creation
{
name: 'region'
value: 'us-east-1' // Some old Dapr versions require this to be set
}

{
name: 'forcePathStyle'
value: true
}

{
name: 'disableSSL'
value: true
}
]
}
}

output result object = {
// This workaround is needed because the deployment engine omits Kubernetes resources from its output.
// This allows Kubernetes resources to be cleaned up when the resource is deleted.
// Once this gap is addressed, users won't need to do this.
resources: [
'/planes/kubernetes/local/namespaces/${svc.metadata.namespace}/providers/core/Service/${svc.metadata.name}'
'/planes/kubernetes/local/namespaces/${minio.metadata.namespace}/providers/apps/Deployment/${minio.metadata.name}'
'/planes/kubernetes/local/namespaces/${daprComponent.metadata.namespace}/providers/dapr.io/Component/${daprComponent.metadata.name}'
]
values: {
type: daprType
version: daprVersion
metadata: daprComponent.spec.metadata
}
}

0 comments on commit bc16b16

Please sign in to comment.