I want to use pulumi transformations to update an environment variable value in a k8s manifest:
containers:
- name: pgbouncer
image: edoburu/pgbouncer:1.15.0
env:
# order of env vars should not change because pulumi code depends on it
- name: DATABASE_URL
value: 'psql://${username}:${password}#${endpoint}/${name}'
- name: POOL_MODE
value: 'transaction'
- name: MAX_CLIENT_CONN
value: '50'
- name: ADMIN_USERS
value: '${username}'
I did it by using the order or the environment variables See the following code, but I don't think it's good to keep it that way for its non-readability and error-prone in the future.
const pg_bouncer_reader = new k8s.yaml.ConfigFile("pg-bouncer-reader", {
file: "../../pulumi-common/pg-bouncer.deployment.yaml",
transformations: [
(obj: any) => {
obj.spec.template.spec.containers[0].env[0].value = readerConnectionString;
obj.spec.template.spec.containers[0].env[3].value = defaultCluster.masterUsername;
},
],
});
is there a way to target YAML list element using syntax like obj.spec.template.spec.containers[0].env[name = 'DATABASE_URL']?
You can simply use any JavaScript method to search through lists. A colleague of mine (shoutout to Levi!) managed this using JavaScripts .find method:
const deployment = new k8s.yaml.ConfigFile("deployment", {
file: "deployment.yaml",
transformations: [
(obj: any) => {
obj.spec.template.spec.containers
.find((container: any) => container.name === "pgbouncer")
.env
.find((env: any) => env.name === "DATABASE_URL")
.value = readerConnectionString;
obj.spec.template.spec.containers
.find((container: any) => container.name === "pgbouncer")
.env
.find((env: any) => env.name === "ADMIN_USERS")
.value = defaultCluster.masterUsername;
},
],
});
Related
If I have a JSON schema saved in a file like f1040.json with content:
[
{
name: "L1",
type: "String"
},
{
name: "C2",
type: "Boolean"
},
{
name: "L3",
type: "String"
},
...
]
And I want to generate a type that looks like:
type F1040 = {
L1: string;
C2: boolean;
L3: string;
...
}
How can I generate this without specifying each field manually (there are hundreds of them)? My first attempt at a solution isn't valid typescript (bc I'm providing more then one mapped type I think) but hopefully this invalid example clarifies what I'm trying to do:
import { f1040 } from "./f1040.json";
const bools = f1040.filter(e => e.type === "Boolean").map(e => name) as string[];
const strings = f1040.filter(e => e.type === "String").map(e => e.name) as string[];
export type F1040 = {
[key in (typeof bools)[number]]?: boolean;
[key in (typeof strings)[number]]?: string;
};
My incorrect solution was inspired by an answer to a similar but different question: TS create keys of new type from array of strings
Edit1: The solution doesn't need to be dynamic but if it's a build-time solution then it needs to play nice with rollup & the declarations bundler I'm using
Edit2: Another unsuccessful attempt, this time utilizing #sinclair/typebox:
import { Static, Type } from "#sinclair/typebox";
import { f1040 } from "./f1040.json";
const F1040 = Type.Object({
...f1040
.filter(e => e.type === "Boolean")
.map(e => e.name)
.reduce((res, f) => ({ ...res, [f]: Type.Boolean() }), {}),
...f1040
.filter(e => e.type === "String")
.map(e => e.name)
.reduce((res, f) => ({ ...res, [f]: Type.String() }), {}),
});
export type F1040 = Static<typeof F1040>;
const f1040Data = {
L1: true,
C2: "Yea",
} as F1040
The above attempt builds fine w/out any error.. which is too bad because the type assignments at the end are wrong. This should fail to build with a TypeError saying something like
Types of property 'L1' are incompatible. Type 'boolean' is not comparable to type 'string'.
It cannot be done dynamically, because your typescipt program is compiled into javascript before it is run, and in javascript all type information is removed. Types are only used in the typescript compilation process.
So you need to have the types before typescript compilation. E.g. using https://www.npmjs.com/package/json-schema-to-typescript
In Typescript (specifically React with hooks), I'm trying to parse some URL hash data from an OAuth callback and utilize it in my components.
I'm able to parse my data by calling window.location.hash
const hash = window.location.hash.substr(1);
const oauthData = hash.split('&')
.map(v => v.split('='))
.reduce((pre, [key, value]) => (
key == 'scope' ? {...pre, [key]: value.split('+')} : {...pre, [key]: value}
), {});
{
"access_token": "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIyMkJCWVkiLCJzdWIiOiI1TkZCTFgiLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJzY29wZXMiOiJyc29jIHJhY3QgcnNldCBybG9jIHJ3ZWkgcmhyIHJudXQgcnBybyByc2xlIiwiZXhwIjoxNTc4NTQ3NzkxLCJpYXQiOjE1NzgyMDQzOTF9.qLl0L5DthFu3NxeLodotPsPljYMWgw1AvKj2_i6zilU",
"user_id": "5NFBLX",
"scope": [
"heartrate",
"nutrition",
"location",
"sleep",
"activity",
"weight",
"social",
"profile",
"settings"
],
"token_type": "Bearer",
"expires_in": "343400"
}
Awesome! Now I want to pass all this information into my component and this is where things get a little haywire and I can't figure out the way to get this data into my component because I break type-safety.
My component is built like this
export interface IOAuthProps {
accessToken: string
userID: string
scope: string[]
expiresIn: number
}
const OAuthFun: React.FC<IOAuthProps> = (props) => {
const [ac] = useState(props.accessToken)
return (
<div>
access token = {ac}
</div>
)
}
export default OAuthFun;
I've tried these permutations of what seem like the same thing (I'll omit the additional properties for brevity):
Nonworking example: can't even index oauthData because it is of type {}
<OAuthFun accessToken={oauthData['access_token'] as string}/>
Since I couldn't even index the raw json object as a dictionary, I figured I needed to create some type safety on the object getting constructed:
const oauthData = hash.split('&')
.map(v => v.split('='))
.reduce((pre, [key, value]) => (
key == 'scope' ? {...pre, [key]: value.split('+')} : {...pre, [key]: value}
), {access_token: String, user_id: String, scope: [], expires_in: Number});
However, this breaks the expression inside my reduce call: No overload matches this call. Which leads me to believe that I need to have some more concise manor of parsing the raw data, but I'm really unsure of how to do that.
I imagine I could cast it directly from raw data, to the interface but the raw data has underscore_casing instead of camelCasing for its naming conventions. Plus it just side-steps the problem without addressing it if I change the casing instead of learning how to normalize the data.
What is the correct approach to get raw data into the interface directly?
Based on the comments, I was able to piece together this solution.
import React from 'react';
export interface IOAuthProps {
accessToken: string
userID: string
scope: string[]
expiresIn: number
}
export function ParseOAuthProperties(rawHashProperties: string): IOAuthProps {
const rawData = rawHashProperties.substr(1)
.split('&')
.map(v => v.split('='))
.reduce((pre, [key, value]) => (
{...pre, [key]: value}
), {access_token: "", user_id: "", scope: "", expires_in: ""});
const normalizedData: IOAuthProps = {
accessToken: rawData.access_token,
userID: rawData.user_id,
scope: rawData.scope.split('+'),
expiresIn: Number(rawData.expires_in),
}
return normalizedData;
}
const OAuthFun: React.FC<IOAuthProps> = (props) => {
return (
<div>
<div>access token = {props.accessToken}</div>
<div>user id = {props.userID}</div>
<div>scope = {props.scope}</div>
<div>expires in = {props.expiresIn}</div>
</div>
)
}
export default OAuthFun;
Now I can take my method, which encapsulates the normalization and returns the interface, and use it from my parent component:
import React from 'react';
import OAuthFun, {ParseOAuthProperties, IOAuthProps} from './OAuthFun'
const App: React.FC = () => {
const props: IOAuthProps = ParseOAuthProperties(window.location.hash)
return (
<div className="App">
{/* Note, you can pass the interface wholesale with the spread operator */}
<OAuthFun {...props} />
</div>
);
}
export default App;
I've just started using feathers to build REST server. I need your help for querying tips. Document says
When used via REST URLs all query values are strings. Depending on the service the values in params.query might have to be converted to the right type in a before hook. (https://docs.feathersjs.com/api/databases/querying.html)
, which puzzles me. find({query: {value: 1} }) does mean value === "1" not value === 1 ? Here is example client side code which puzzles me:
const feathers = require('#feathersjs/feathers')
const fetch = require('node-fetch')
const restCli = require('#feathersjs/rest-client')
const rest = restCli('http://localhost:8888')
const app = feathers().configure(rest.fetch(fetch))
async function main () {
const Items = app.service('myitems')
await Items.create( {name:'one', value:1} )
//works fine. returns [ { name: 'one', value: 1, id: 0 } ]
console.log(await Items.find({query:{ name:"one" }}))
//wow! no data returned. []
console.log(await Items.find({query:{ value:1 }})) // []
}
main()
Server side code is here:
const express = require('#feathersjs/express')
const feathers = require('#feathersjs/feathers')
const memory = require('feathers-memory')
const app = express(feathers())
.configure(express.rest())
.use(express.json())
.use(express.errorHandler())
.use('myitems', memory())
app.listen(8888)
.on('listening',()=>console.log('listen on 8888'))
I've made hooks, which works all fine but it is too tidious and I think I missed something. Any ideas?
Hook code:
app.service('myitems').hooks({
before: { find: async (context) => {
const value = context.params.query.value
if (value) context.params.query.value = parseInt(value)
return context
}
}
})
This behaviour depends on the database and ORM you are using. Some that have a schema (like feathers-mongoose, feathers-sequelize and feathers-knex), will convert values like that automatically.
Feathers itself does not know about your data format and most adapters (like the feathers-memory you are using here) do a strict comparison so they will have to be converted. The usual way to deal with this is to create some reusable hooks (instead of one for each field) like this:
const queryToNumber = (...fields) => {
return context => {
const { params: { query = {} } } = context;
fields.forEach(field => {
const value = query[field];
if(value) {
query[field] = parseInt(value, 10)
}
});
}
}
app.service('myitems').hooks({
before: {
find: [
queryToNumber('age', 'value')
]
}
});
Or using something like JSON schema e.g. through the validateSchema common hook.
I have to update all the elements in an array using Immutablejs
The JSON looks like this :
imState = Immutable.fromJS({
app: {
line: {
name: "Bar Chart",
series: [
{
name: "Series1x",
color: "#82ca9d"
},
{
name: "Series2x",
color: "#2239ca"
},
{
name: "Series3x",
color: "#c2a5ca"
}
]
}
}
})
And I would simply like to iterate over all the series elements and change the color to a fixed color "#1bf115".
I am guessing you would use the update function. There is no API documentation on this function so I have been doing a lot of trial an error.
I tried to use it like this :
imState = imState.update(
['app', 'line', 'series'],
series => series.map(s => s.update('color', color => "#1bf115"))
)
However I get an undefined error at series.map.
Why is this wrong?
Because you are supplying a deeply nested path , instead of update use updateIn.
imState = imState.updateIn(
['app', 'line', 'series'],
series => series.map(s => s.update('color', color => "#1bf115"))
)
I am working with a dataset that cannot be modified on the server side. So I am trying to setup the local data model on the client in a way that I can easily traverse through the model when updating parts of the data.
Therefore I am trying to create a multi-leveled Map from multi-leveled Maps including Lists, that themselves include Maps, etc. (see schematics at the end of this post).
What I am trying to get is a Map containing other Maps, with the key of the included Map being the value of the object (again please see schematics at the end of this post).
I got it to work on the first level:
const firstLevel = data.toMap().mapKeys((key, value) => value.get('value'));
See it in action here: https://jsfiddle.net/9f0djcb0/4/
But there is a maximum of 3 levels of nested data and I can't get my head around how to get the transformation done. Any help appreciated!
The schematic datasets:
// This is what I got
const dataset = [
{
field: 'lorem',
value: 'ipsum',
more: [
{
field: 'lorem_lvl1',
value: 'ispum_lvl1',
more: [
{
field: 'lorem_lvl2',
value: 'ispum_lvl2',
more: [
{
field: 'lorem_lvl3',
value: 'ispum_lvl3',
}
]
}
]
}
]
},
{
field: 'glorem',
value: 'blipsum'
},
{
field: 'halorem',
value: 'halipsum'
}
];
This is where I want to go:
// This is what I want
const dataset_wanted = {
ipsum: {
field: 'lorem',
value: 'ipsum',
more: {
lorem_lvl1: {
field: 'lorem_lvl1',
value: 'ispum_lvl1',
more: {
lorem_lvl2: {
field: 'lorem_lvl2',
value: 'ispum_lvl2',
more: {
lorem_lvl3: {
field: 'lorem_lvl3',
value: 'ispum_lvl3',
}
}
}
}
}
}
},
glorem: {
field: 'glorem',
value: 'blipsum'
},
halorem: {
field: 'halorem',
value: 'halipsum'
}
};
Retrieve nested structures using "getIn" is beter.
const data = Immutable.fromJS(dataset[0]);
const firstLevel = data.getIn(['more']);
const twoLevel = firstLevel.getIn([0,'more']);
const threeLevel = twoLevel.getIn([0,'more']);
console.log(firstLevel.toJS(),twoLevel.toJS(),threeLevel.toJS());
As for a more generative solution, I re-wrote the answer before to a recursive approach:
function mapDeep(firstLevel) {
return firstLevel.map((obj) => {
if (obj.has('more')) {
const sec = obj.get('more').toMap().mapKeys((key, value) => value.get('value'));
const objNext = mapDeep(sec);
obj = obj.set('more', objNext);
}
return obj;
});
}
The first level still needs to be mapped manually before.
const firstLevel = data.toMap().mapKeys((key, value) => value.get('value'));
const secondLevel = mapDeep(firstLevel);
Again, see it in action: https://jsfiddle.net/9f0djcb0/12/
This is good enough for me for now. Still feels like this can be solved smarter (and more performant).. Cheers :)
So after some time passed I came up with a solution that works for me:
let sec, third, objThird;
// 1st level: simple mapping
const firstLevel = data.toMap().mapKeys((key, value) => value.get('value'));
// 2nd level: walk through updated firstLevel's subobjects and do the mapping again:
const secondLevel = firstLevel.map((obj) => {
if (obj.has('more')) {
sec = obj.get('more').toMap().mapKeys((key, value) => value.get('value'));
// 3nd level: walk through updated secondLevel's subobjects and do the mapping again:
objThird = sec.map((o) => {
if (o.has('more')) {
third = o.get('more').toMap().mapKeys((key, value) => value.get('value'));
o = o.set('more', third);
}
return o;
});
obj = obj.set('more', objThird);
}
return obj;
});
See it in action here: https://jsfiddle.net/9f0djcb0/7/
This has been working nicely so far, thur pretty hard-coded. If anyone has a more elegant solution to this, I am happy to learn about it!